178 Commits
v2 ... v2.0.3

Author SHA1 Message Date
Chris
815895a524 Merge branch 'master' of https://github.com/HaschekSolutions/pictshare 2025-05-06 13:42:07 +02:00
Chris
de764c105c cf ip forward 2025-05-05 20:15:39 +02:00
Christian Haschek
749e7f193f Merge pull request #159 from YellingTree/patch-1
Change Input field to password type
2024-11-10 16:17:30 +01:00
Nixie
2cda214f86 Change Input field to password type
Change the input box for UPLOAD_CODE from type 'text' to type 'password' to hide the upload code while on-screen and signal to browsers to prompt to save the code in their password managers
2024-10-30 13:35:09 -04:00
Christian Haschek
f62b30d696 Merge pull request #154 from metalefty/fork-me
Fix dead image reference to "Fork me on GitHub"
2024-04-21 09:02:35 +02:00
Koichiro Iwao
b07a68bb45 Put gh-fork-ribbon locally for GDPR compliance 2024-04-20 21:32:46 +09:00
Koichiro Iwao
2b0dc071a0 Fix dead image reference to "Fork me on GitHub"
Switched to CSS implementation hosted on cdsjs.com. It should be
more permanent than githubusercontent.com.

Resolves: #153
2024-04-20 21:27:01 +09:00
Christian Haschek
f2cf56ceaa Merge pull request #155 from metalefty/passing-null
Fix for deprecated features in PHP 8.1
2024-04-15 13:04:39 +02:00
Koichiro Iwao
783f996e77 Fix for deprecated features in PHP 8.1
Deprecated: strpos(): Passing null to parameter #1 ($haystack) of type string

ref.  https://www.php.net/manual/en/migration81.deprecated.php#migration81.deprecated.core.null-not-nullable-internal
2024-03-07 22:47:39 +09:00
Christian Haschek
17dfb43abd Merge pull request #151 from HaschekSolutions/dependabot/composer/lib/aws/aws-sdk-php-3.288.1
Bump aws/aws-sdk-php from 3.33.4 to 3.288.1 in /lib
2023-12-22 07:53:12 +01:00
dependabot[bot]
1693bcaa78 Bump aws/aws-sdk-php from 3.33.4 to 3.288.1 in /lib
Bumps [aws/aws-sdk-php](https://github.com/aws/aws-sdk-php) from 3.33.4 to 3.288.1.
- [Release notes](https://github.com/aws/aws-sdk-php/releases)
- [Commits](https://github.com/aws/aws-sdk-php/compare/3.33.4...3.288.1)

---
updated-dependencies:
- dependency-name: aws/aws-sdk-php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-22 00:27:17 +00:00
Chris
bc1eca4014 wrong project 2023-11-13 10:30:25 +01:00
Chris
fc9a7d3072 Merge branch 'master' of https://github.com/HaschekSolutions/pictshare 2023-11-13 10:27:47 +01:00
Chris
67fba8e66b updated CI to include versions 2023-11-13 10:27:40 +01:00
Christian Haschek
7f18a193d4 Update core.php
added override for host if it doesn't contain a port
2023-11-04 18:14:55 +01:00
Christian Haschek
090f452585 fixed start.sh 2023-11-04 18:01:43 +01:00
Chris
819c25374c silent rollout of split data dir 2023-11-03 16:48:12 +01:00
Chris
2388f68d5d preparations for dynamic data directories 2023-11-02 23:20:05 +01:00
Chris
3fe84a63a4 Merge branch 'master' of https://github.com/HaschekSolutions/pictshare 2023-11-02 20:23:16 +01:00
Chris
774c8e0112 disable follow to avoid CVEs 2023-11-02 20:23:01 +01:00
Christian Haschek
63132e1cfb Create SECURITY.md 2023-11-02 16:44:00 +01:00
Chris
d349ee8fc6 added check for private ip range 2023-11-02 08:14:25 +01:00
Chris
5c3ee9e159 preparations for queue-rendering 2023-10-15 20:31:01 +02:00
Chris
cea501d854 more minot php8 warning fixes 2023-10-15 20:30:12 +02:00
Chris
a1bc5b5fa5 minor warning adjustments 2023-10-15 20:17:56 +02:00
Chris
d3d5d1c385 Upgraded to php8.2 🎉🎉🎉 2023-09-14 10:42:35 +02:00
Chris
f000404d5a hardcoded registries and fixed typos 2023-09-13 08:40:34 +02:00
Chris
c777cd62ab missed docker.io url 2023-09-13 08:32:47 +02:00
Chris
06a524ca2f mussing var 2023-09-13 08:32:10 +02:00
Chris
2712946287 changed pipeline so it's also pushed to docker hub 2023-09-13 08:26:42 +02:00
Chris
fffcb13ac9 fixed conversion bug 2023-09-07 17:55:25 +02:00
Chris
307243a5bf only add when upload coede is configured 2023-09-07 17:45:04 +02:00
Chris
61acb5420b implemented UPLOAD_CODE variable
If configured, needs a code as POST/GET variable for every upload. Also adds a input field to the main template
2023-09-06 15:57:15 +02:00
Chris
cccb80de03 remove dynamic extensions 2023-09-06 15:35:12 +02:00
Chris
d0deb6f6c9 noo need to convert anymore since it's the right header now 2023-09-03 10:38:45 +02:00
Chris
e551ef2bb2 added json header closes #146 2023-09-03 10:35:13 +02:00
Chris
21aa1fbb7d added identicons 2023-09-03 10:34:35 +02:00
Chris
4c1124da07 finally removed the static s3php files and replaced with composer 2023-09-03 10:09:43 +02:00
Chris
19d7cb060e added support for dynamic content controllers
Also implemented a placeholder generator to showcase it
2023-08-26 22:39:08 +02:00
Chris
c90a716d45 ALWAYS_WEBP now also for PNG 2023-08-26 11:55:00 +02:00
Chris
4fbf8b31a2 fixed missing config generator 2023-08-25 21:53:55 +02:00
Chris
422c17eb65 Added new setting to force JPGs sent as WebP if supported by the client 2023-08-24 23:09:59 +02:00
Christian Haschek
70cdcf5dcf Merge pull request #150 from beabee-communityrm/feat/jpeg-orientation
Add JPEG EXIF orientation rotation
2023-08-24 21:35:29 +02:00
Will Franklin
7b4b27098d Add support for flipping images too 2023-08-24 16:48:44 +01:00
Will Franklin
2362af1e8c Fix docker build instructions 2023-08-24 15:41:32 +01:00
Will Franklin
28f5677247 Rotate JPEG images if they have an Orientation tag 2023-08-24 15:41:24 +01:00
Christian Haschek
8c2702be96 Merge pull request #147 from gabe565/svg-logo
Add SVG logo
2023-04-17 11:03:30 +02:00
Gabe Cook
452db23c57 Use SVG logo in readme and tweak formatting 2023-04-15 16:45:52 -05:00
Gabe Cook
c6baa6edfa Add SVG logo 2023-04-15 16:45:41 -05:00
Christian Haschek
74ccf9f626 updated info on new docker container source 2023-03-23 09:52:23 +01:00
Christian Haschek
56f89d5610 upgraded login action tag to avoid deprecation 2023-03-23 09:26:43 +01:00
Christian Haschek
0ea4b3c160 is this a magic variable? let's find out 2023-03-22 23:01:41 +01:00
Christian Haschek
93d78d44fe make image name lowercase otherwise will faild github build 2023-03-22 22:57:29 +01:00
Christian Haschek
7dac978d16 dynamic image name based on envs 2023-03-22 22:53:47 +01:00
Christian Haschek
312f18ade2 move to github repository - check 2023-03-22 22:50:13 +01:00
Chris H
cb2d17411c small undefined variable bug fixed 2023-01-16 10:28:54 +01:00
Chris H
9a4a20fb41 typo . fixes #142 2023-01-13 09:01:37 +01:00
Christian Haschek
72394f17ba Merge pull request #140 from FN-Florian/master
Add S3_REDGION varaible
2022-06-27 08:19:06 +02:00
FN-Florian
240c7359a8 Update s3.controller.php 2022-06-27 00:08:24 +02:00
FN-Florian
9d8d5f3d36 Update start.sh 2022-06-27 00:07:32 +02:00
FN-Florian
25b39bbd86 Update CONFIG.md 2022-06-25 23:56:31 +02:00
FN-Florian
4964967524 Update DOCKER.md 2022-06-25 23:55:06 +02:00
FN-Florian
bfb43ab856 Update s3.controller.php
add S3_REGION variable
2022-06-20 08:50:13 +02:00
Christian Haschek
03464199ee Merge pull request #138 from quonic/master
Adds PowerShell script for Greenshot on Windows
2022-05-12 08:10:49 +02:00
Christian Haschek
96e73e2baf Merge branch 'master' into master 2022-05-12 08:10:37 +02:00
Christian Haschek
6ad94f2063 added php function for uploading 2022-05-04 22:46:59 +02:00
Jim Caten
45b2e11729 Adds window style Hidden to PowerShell script 2022-04-21 02:15:26 -05:00
Jim Caten
c7f8f37c62 Adds PowerShell script for Greenshot
Curl is needed as Invoke-WebRequest and Invoke-RestMethod don't like uploading files easily. Curl simplifies the process down to one line of code.
2022-04-21 02:07:01 -05:00
Christian Haschek
6f540ad27e added copy url button closes #112 2022-03-08 22:23:57 +01:00
Christian Haschek
ef1a1ecaff disable debug output 2022-02-23 19:19:53 +01:00
Christian Haschek
be5f601866 disable upload timeout so big uploads wont fail 2022-02-23 19:19:38 +01:00
Christian Haschek
45d933ebf0 fixing gui-upload filesize bug 2022-02-23 19:11:33 +01:00
Christian Haschek
a145ca88cc was meant to strpos 2022-02-19 10:53:26 +01:00
Christian Haschek
cf0effd0e8 allowing for multiple comma separated CIDR ranges 2022-02-19 10:47:07 +01:00
Christian Haschek
8ee26e6922 added small ip debug info output 2022-02-19 10:38:50 +01:00
Christian Haschek
9d1a4db4d2 updated cloudflare ips 2022-02-19 10:36:54 +01:00
Christian Haschek
adb1c468cd added ipv6 support in ALLOWED_SUBNET variable 2022-02-19 08:56:29 +01:00
Christian Haschek
d6f3b7b5df Merge branch 'master' of github.com:HaschekSolutions/pictshare 2022-01-31 10:01:56 +01:00
Christian Haschek
33b08f378d added libz 2022-01-31 10:01:54 +01:00
Christian Haschek
31d2712733 added demo gif again 2022-01-31 09:19:24 +01:00
Christian Haschek
9200d3d93c added setting and possible fix for passive mode. fixes #135 probably 2022-01-24 15:23:38 +01:00
Christian Haschek
b669e69511 if p1 was done then don't ask the server for every individual file again 2021-11-24 23:30:37 +01:00
Christian Haschek
f0cfdeb650 fixed build bug 2021-11-22 12:46:35 +01:00
Christian Haschek
99d9542b80 fixed typo that caused 500 error 2021-11-22 12:43:50 +01:00
Christian Haschek
b65186f77f removed static ffmpeg binary and updated readme files 2021-11-22 11:48:01 +01:00
Christian Haschek
3ee37fe3a2 updates that make life easier on ARM or low memory devices 2021-11-20 23:08:47 +01:00
Christian Haschek
5ecf80bce1 fixed dockerfile path 2021-11-20 01:48:51 +01:00
Christian Haschek
5a5c4a0334 building for arm and x64 now (if it works) 2021-11-20 01:47:17 +01:00
Christian Haschek
7c7358af9c only save one tag per day 2021-11-20 01:25:43 +01:00
Christian Haschek
ebc4ebda03 automatic tags are now yyyymmddhhmmss 2021-11-20 01:21:08 +01:00
Christian Haschek
a4dc4366ba new path for stored files in docker container!!! 2021-11-11 23:42:33 +01:00
Christian Haschek
ea40ffbc46 Unified docker building process. & more updates
- automatic builds
- automated tags on docker
- no more auto update (update your docker containers)
- auto not removed of mp4 anymore
- bug fixes

also closes #85 and closes #124
2021-11-11 23:39:12 +01:00
Christian Haschek
9a7fadb231 streamlined hash detection 2021-11-11 19:17:21 +01:00
Christian Haschek
199f162fdf Merge pull request #128 from ranjit-git/master
Update core.php
2021-06-18 07:46:51 +02:00
ranjit-git
70c06cfa5c Update core.php 2021-06-11 10:15:46 +05:30
Christian Haschek
520099be96 Merge pull request #126 from vikbez/patch-1
"file" command is required
2021-05-22 09:26:58 +02:00
Romain Gay
5cf9b86868 "file" command is required 2021-05-21 22:35:24 +02:00
Christian Haschek
03d4875e92 Update README.md
new hit count
2021-04-23 08:55:50 +02:00
Christian Haschek
726c77effb only use finfo on windows. fixes #125 2021-03-10 08:30:02 +01:00
Christian Haschek
68de83b46a fixed ftp delete 2021-01-19 11:27:21 +01:00
Christian Haschek
77d4023f00 missing part for last fix 2020-07-27 09:40:30 +02:00
Christian Haschek
0119368376 catching non-configured subnet bug 2020-07-15 20:36:55 +02:00
Chris
166ff1da1b fixed filter examples 2020-06-23 12:11:57 +02:00
Chris
7727fc9ea4 filters already implemented 2020-06-23 12:06:24 +02:00
Chris
8de53d1ea7 added warning message if upload forbidden instead of just 401 error 2020-06-23 09:26:39 +02:00
Chris
0250b6a577 slicker check for upload permissions, included http response code 2020-06-23 09:17:49 +02:00
Chris
e13f4816fb allowing multiple ip ranges in checking function 2020-06-15 10:47:57 +02:00
Chris
75784174fa extended subnet access control 2020-06-15 10:38:07 +02:00
Chris
6d504f3a48 added support for cloudflare IP logging 2020-06-11 01:16:55 +02:00
Chris
3a6c987347 allow rendering of existing files 2020-06-11 01:05:11 +02:00
Chris
5861e73848 implemented dynamic content controller loading, enabling whitelisting of content types. closes #87 2020-06-06 13:27:47 +02:00
Chris
a2b7feb6f9 changed to oneliner 2020-06-06 11:53:22 +02:00
Chris
dce00906ec added URI fallback. This enables pictshare to be run directly from "php -S localhost:8000" 2020-06-06 11:52:34 +02:00
Chris
ceffa04b6e implemented tag and last-modified for images. closes #119 2020-06-03 00:38:05 +02:00
Chris
984246912c added possibility to have a notice displayed on the upload form 2020-05-31 19:03:59 +02:00
Chris
3a8ac33dd1 updated encryption md 2020-05-31 17:43:28 +02:00
Chris
7e28024d32 added pasting of images. closes #117 2020-05-13 16:09:57 +02:00
Chris
b149fe88cb added integrations and updated readme 2020-01-13 01:01:56 +01:00
Chris
3a27592d58 fixed queue 2020-01-13 00:31:19 +01:00
Chris
80e210af86 implemented simple queue of hashes that were not successfully uploaded to a storage container 2020-01-13 00:28:44 +01:00
Chris
220a3103fa cleaning of unused scripts 2020-01-13 00:28:23 +01:00
Chris
6ec765024e fixed dev output 2020-01-12 13:38:27 +01:00
Chris
6c556e921f added ftp_ssl support (not sftp!) 2020-01-11 15:20:38 +01:00
Chris
3a90787091 use passive mode by default 2020-01-11 02:40:11 +01:00
Chris
c4087c1e84 fixed bug that wouldnt allow creation of folders name 0 2020-01-11 02:32:53 +01:00
Chris
76c0f6cec1 allow skipping of phase 1 2020-01-11 02:19:08 +01:00
Chris
f01b685820 changed ftp server storage to distribute evenly across directories 2020-01-11 01:54:59 +01:00
Chris
e022227617 typo 2020-01-10 14:32:46 +01:00
Chris
2fc56d438d easier on the eyes 2020-01-10 14:30:07 +01:00
Chris
fb50f23e19 added new storage controller (FTP) and updated config 2020-01-10 14:25:26 +01:00
Chris
40c53173ca updated readme for config info 2020-01-10 12:45:01 +01:00
Chris
30c78ab7da increased default key polls 2020-01-08 22:47:05 +01:00
Chris
5e59d7e80d forgot dev flag on s3 controller 2020-01-08 10:36:57 +01:00
Chris
dd0d274ab1 debug var 2020-01-08 10:31:07 +01:00
Chris
3c7e8bea7c better loop 2020-01-08 10:28:44 +01:00
Chris
1a46209bfe with class name it's easier 2020-01-08 09:28:30 +01:00
Chris
ad04aeb5b6 for status messages 2020-01-08 09:10:43 +01:00
Chris
bbb0b5b111 sync script changed to pull first then push 2020-01-08 00:08:10 +01:00
Chris
a19d0a3d48 sync script added and fixed some controllers 2020-01-07 22:13:29 +01:00
Chris
63fe1b3edd readme link fixed again 2020-01-07 19:08:02 +01:00
Chris
b9e42d0f31 fixed link 2020-01-07 19:07:22 +01:00
Chris
021fbad811 Added encryption based on Libsodium 2020-01-07 19:05:22 +01:00
Chris
6cab15f8e7 added example config setting 2020-01-05 03:13:04 +01:00
Chris
f863045fcf S3/Minio storage controller Support 2020-01-05 03:11:41 +01:00
Christian Haschek
87fa55eae8 Merge pull request #98 from dessalines/master
Add docker-compose.yml
2019-11-07 11:29:04 +01:00
Dessalines
6806e4a15a Add docker-compose.yml
Fixes #93
2019-09-23 13:32:33 -07:00
Chris
82f97479ff Merge branch 'master' of https://github.com/HaschekSolutions/pictshare 2019-08-21 21:06:08 +02:00
Chris
75124102cb added docker shields 2019-08-21 21:05:48 +02:00
Christian Haschek
515e034d91 Merge pull request #86 from thomasjsn/master
Add commandline alias examples and modifiers documentation
2019-03-08 14:04:43 +01:00
Thomas Jensen
2a628e7d7a layout changes to modifiers documentation 2019-03-08 09:50:46 +01:00
Thomas Jensen
8e3cf866a6 add modifisers documentation 2019-03-08 09:48:02 +01:00
Thomas Jensen
6adb557693 Add commandline alias examples 2019-03-08 09:46:35 +01:00
Christian Haschek
c178f3af44 Merge pull request #84 from rumkit/client-subnet-filter
Client subnet filter
2019-02-20 17:18:14 +01:00
Anton Mitsengendler
8da3573ffb added client subnet filter 2019-02-20 17:12:06 +03:00
Chris
5f5b836a22 added base64 readme 2019-01-14 20:42:14 +01:00
Chris
17974088bc dont need to redeclare 2019-01-12 13:41:14 +01:00
Chris
4d5d57df8a fixed altfolder for v2 2019-01-12 13:38:00 +01:00
Christian Haschek
71df6a54c4 Update README.md 2018-12-30 23:40:13 +01:00
Christian Haschek
4a5e29e7c1 Update README.md 2018-12-30 23:37:08 +01:00
Christian Haschek
9d2bf6a931 Update README.md 2018-12-30 23:34:24 +01:00
Christian Haschek
66eebd1445 Update README.md 2018-12-30 23:25:34 +01:00
Christian Haschek
38f6b1c22f Update README.md 2018-12-30 23:24:46 +01:00
Christian Haschek
263f0b762b Update README.md 2018-12-30 23:24:34 +01:00
Christian Haschek
9f9449a58d Update README.md 2018-12-30 23:23:07 +01:00
Christian Haschek
1cb8e28be7 Update README.md 2018-12-30 23:21:25 +01:00
Christian Haschek
37e0ae26f8 Update SCALING.md 2018-12-30 22:50:29 +01:00
Chris
d41e06efa0 clarification 2018-12-30 19:29:51 +01:00
Chris
772860e186 updated readme for image filters 2018-12-30 19:28:29 +01:00
Chris
5cc690bbee added filters 2018-12-30 19:24:56 +01:00
Chris
b2a7c778d8 added new api and updated readme 2018-12-30 12:50:52 +01:00
Chris
771ec3305e typo 2018-12-29 11:17:00 +01:00
Chris
f4cea4ce08 added dependencies thanks to #79 2018-12-29 11:15:55 +01:00
Chris
66570c54d8 more features 2018-12-28 20:48:30 +01:00
Chris
ee0000bf59 hint hint ;D 2018-12-28 20:45:22 +01:00
Chris
5d6b57cabc fixed but which prevented files form being deleted 2018-12-28 18:06:31 +01:00
Chris
b3e777baa3 not dev anymore 2018-12-27 19:40:13 +01:00
Chris
fb25657d2f Merge branch 'v2' 2018-12-27 19:04:54 +01:00
Christian Haschek
3e6cfdfed6 Merge pull request #78 from HaschekSolutions/v2
V2
2018-12-27 18:54:46 +01:00
60 changed files with 3940 additions and 550 deletions

7
.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
tmp/*
inc/config.inc.php
data/*
lib/vendor
bin
.git
.github

59
.github/workflows/build-docker.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: ci
on:
push:
tags:
- "v*.*.*"
pull_request:
branches:
- "master"
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
hascheksolutions/pictshare
ghcr.io/hascheksolutions/pictshare
# generate Docker tags based on the following events/attributes
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

5
.gitignore vendored
View File

@@ -1 +1,4 @@
test.php
test.php
notice.txt
lib/vendor
.vscode

3
CHANGELOG.md Normal file
View File

@@ -0,0 +1,3 @@
# V2.0.0 (Nov 2023)
- Pushed current release to version 2.0
- Updated CI to include versions

View File

@@ -1,42 +1,78 @@
# PictShare v2
---
[![Apache License](https://img.shields.io/badge/license-Apache-blue.svg?style=flat)](https://github.com/HaschekSolutions/pictshare/blob/master/LICENSE)
<p align="center">
<a href="" rel="noopener">
<img height=200px src="./css/imgs/logo/logo.svg" alt="PictShare logo">
</a>
</p>
# This is the development branch for Version 2 do not use in production
Test site: https://dev.pictshare.net/ (only sometimes on)
<h1 align="center">PictShare</h1>
<h4 align="center">https://pictshare.net</h4>
<div align="center">
![](https://img.shields.io/badge/php-8.2%2B-brightgreen.svg)
[![](https://img.shields.io/docker/pulls/hascheksolutions/pictshare?color=brightgreen)](https://hub.docker.com/r/hascheksolutions/pictshare)
![](https://github.com/hascheksolutions/pictshare/actions/workflows/build-docker.yml/badge.svg)]
[![Apache License](https://img.shields.io/badge/license-Apache-brightgreen.svg?style=flat)](https://github.com/HaschekSolutions/pictshare/blob/master/LICENSE)
[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fhascheksolutions%2Fpictshare&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com)
[![](https://img.shields.io/github/stars/HaschekSolutions/pictshare.svg?label=Stars&style=social)](https://github.com/HaschekSolutions/pictshare)
#### Host your own `images` `gifs` `mp4s` `text bins` and stay in control
</div>
-----------------------------------------
<center>
<p align="center">
<img src="https://www.pictshare.net/39928d8239.gif" alt="PictShare demo">
</p>
Table of contents
=================
* [Quick Start](#quickstart)
* [Features](#features)
* [Installation](/rtfm/INSTALL.md)
* [Configuration](/rtfm/CONFIG.md)
* [Docker](/rtfm/DOCKER.md)
* [API](/rtfm/API.md)
* [Addons/Integration](/rtfm/INTEGRATIONS.md)
* [Development status](#development-status)
* [Addons and integration](/rtfm/INTEGRATIONS.md)
* [Development roadmap](#development-roadmap)
---
## New Features in v2
## Quickstart
```bash
docker run -d -p 8080:80 --name=pictshare ghcr.io/hascheksolutions/pictshare
```
Then open http://localhost:8080 in your browser
## New Features
- Generate identicons based on strings in the URL [example1](https://pictshare.net/identicon/example1) [example2](https://pictshare.net/identicon/example2)
- Generate placeholder images by specifying the size in the URL. [example](https://pictshare.net/placeholder/555x250/color-white-blue)
- Added support for external storage
- [Encryption of files in external storage](/rtfm/ENCRYPTION.md)
- Added text hosting (like pastebin)
- Added URL shortening
- Added WebP to images (and conversion from jpg,png to webp)
- Added WebP to images (and automatic conversion from jpg, png to webp if the requesting browser supports it)
- Massive code rework. Actually we designed it from the ground up to be more modular and easier to debug
## Breaking changes in v2
- [New API system](/rtfm/API.md). Only single file uploads now via /api/upload.php (POST var name is "file"). [read more..](/rtfm/API.md)
- Data directory renamed from ```upload``` to ```data```
- Backblaze support dropped for now because we didn't need it anymore as ALT_FOLDER is more flexible. If soneone needs it, it can easily be implemented via adding a new storage controller. We're happy to accept pull requests
# Features
- Selfhostable
- [Simple upload API](/rtfm/API.md)
- 100% file based - no database needed
- [Scalable hosting](/rtfm/SCALING.md)
- Many [Filters](/rtfm/IMAGEFILTERS.md) for images
- GIF to MP4 conversion
- JPG, PNG to WEBP conversion
- MP4 resizing
- PictShare removes all exif data so you can upload photos from your phone and all GPS tags and camera model info get wiped
- Change and resize your uploads just by editing the URL
- Change and resize your images and videos just by editing the URL
- Duplicates don't take up space. If the exact same file is uploaded twice, the second upload will link to the first
- Many [configuration options](/rtfm/CONFIG.md)
- Full control over your data. Delete images with individual and global delete codes
@@ -67,17 +103,17 @@ Read [here](/rtfm/CONFIG.md) what those options do
- [x] MASTER_DELETE_CODE
- [x] MASTER_DELETE_IP
- [x] UPLOAD_FORM_LOCATION
- [x] S3 Backend
- [x] UPLOAD_CODE
- [ ] UPLOAD_QUOTA
- [ ] UPLOAD_CODE
- [ ] LOW_PROFILE
- [ ] IMAGE_CHANGE_CODE
- [ ] MAX_RESIZED_IMAGES
- [ ] ALLOW_BLOATING
- [ ] BACKBLAZE
### Image hosting
- [X] Resizing
- [ ] Filters
- [x] Resizing
- [x] Filters
- [x] Gif to mp4 conversion
- [x] Upload of images
@@ -102,4 +138,4 @@ Read [here](/rtfm/CONFIG.md) what those options do
This is a [HASCHEK SOLUTIONS](https://haschek.solutions) project
[![HS logo](https://pictshare.net/css/imgs/hs_logo.png)](https://haschek.solutions)
[![HS logo](https://pictshare.net/css/imgs/hs_logo.png)](https://haschek.solutions)

11
SECURITY.md Normal file
View File

@@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
PictShare uses rolling releases so the latest version is the only supported one.
## Reporting a Vulnerability
Reports can be made as issues in this repo or in confidence via christian@haschek.at
Pull requests welcome

View File

@@ -3,6 +3,8 @@
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).'/..');
header('Content-Type: application/json; charset=utf-8');
//loading default settings if exist
if(!file_exists(ROOT.DS.'inc'.DS.'config.inc.php'))
exit('Rename /inc/example.config.inc.php to /inc/config.inc.php first!');
@@ -10,13 +12,16 @@ include_once(ROOT.DS.'inc'.DS.'config.inc.php');
//loading core and controllers
include_once(ROOT . DS . 'inc' . DS. 'core.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'image'. DS . 'image.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'text'. DS . 'text.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'url'. DS . 'url.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'video'. DS . 'video.controller.php');
//load external things if existing
if(file_exists(ROOT.'/lib/vendor/autoload.php'))
require ROOT.'/lib/vendor/autoload.php';
loadAllContentControllers();
// check if client has permission to upload
executeUploadPermission();
// check write permissions first
if(!isFolderWritable(ROOT.DS.'data'))
if(!isFolderWritable(getDataDir()))
exit(json_encode(array('status'=>'err','reason'=>'Data directory not writable')));
else if(!isFolderWritable(ROOT.DS.'tmp'))
exit(json_encode(array('status'=>'err','reason'=>'Temp directory not writable')));
@@ -33,14 +38,15 @@ if($_REQUEST['base64'])
base64ToFile($data, $tmpfile);
//get the file type
$type = getTypeOfFile($tmpfile);
//check for duplicates
$sha1 = sha1_file($tmpfile);
$ehash = sha1Exists($sha1);
if($ehash && file_exists(ROOT.DS.'data'.DS.$ehash.DS.$ehash))
exit(json_encode(array('status'=>'ok','hash'=>$ehash,'filetype'=>$type,'url'=>URL.$ehash)));
if($ehash && file_exists(getDataDir().DS.$ehash.DS.$ehash))
exit(json_encode(array('status'=>'ok','hash'=>$ehash,'filetype'=>$type,'url'=>getURL().$ehash)));
@@ -74,16 +80,10 @@ if($_REQUEST['base64'])
if(getDeleteCodeOfHash($answer['hash']))
{
$answer['delete_code'] = getDeleteCodeOfHash($answer['hash']);
$answer['delete_url'] = URL.'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
$answer['delete_url'] = getURL().'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
}
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
(new $contr())->pushFile($answer['hash']);
}
storageControllerUpload($answer['hash']);
}
echo json_encode($answer);

View File

@@ -3,6 +3,8 @@
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).'/..');
header('Content-Type: application/json; charset=utf-8');
//loading default settings if exist
if(!file_exists(ROOT.DS.'inc'.DS.'config.inc.php'))
exit('Rename /inc/example.config.inc.php to /inc/config.inc.php first!');
@@ -10,13 +12,16 @@ include_once(ROOT.DS.'inc'.DS.'config.inc.php');
//loading core and controllers
include_once(ROOT . DS . 'inc' . DS. 'core.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'image'. DS . 'image.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'text'. DS . 'text.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'url'. DS . 'url.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'video'. DS . 'video.controller.php');
//load external things if existing
if(file_exists(ROOT.'/lib/vendor/autoload.php'))
require ROOT.'/lib/vendor/autoload.php';
loadAllContentControllers();
// check if client has permission to upload
executeUploadPermission();
// check write permissions first
if(!isFolderWritable(ROOT.DS.'data'))
if(!isFolderWritable(getDataDir()))
exit(json_encode(array('status'=>'err','reason'=>'Data directory not writable')));
else if(!isFolderWritable(ROOT.DS.'tmp'))
exit(json_encode(array('status'=>'err','reason'=>'Temp directory not writable')));
@@ -25,6 +30,9 @@ $hash = sanatizeString(trim($_REQUEST['hash']))?sanatizeString(trim($_REQUEST['h
$url = trim($_REQUEST['url']);
if(checkURLForPrivateIPRange($url))
exit(json_encode(array('status'=>'err','reason'=>'Private IP range')));
if(!$url || !startsWith($url, 'http'))
exit(json_encode(array('status'=>'err','reason'=>'Invalid URL')));
@@ -34,15 +42,23 @@ else if(remote_filesize($url)*0.000001 > 20)
$name = basename($url);
$tmpfile = ROOT.DS.'tmp'.DS.$name;
file_put_contents($tmpfile,file_get_contents($url));
$context = stream_context_create(
array(
"http" => array(
"follow_location" => false,
),
)
);
file_put_contents($tmpfile,file_get_contents($url, false, $context));
$type = getTypeOfFile($tmpfile);
//check for duplicates
$sha1 = sha1_file($tmpfile);
$ehash = sha1Exists($sha1);
if($ehash && file_exists(ROOT.DS.'data'.DS.$ehash.DS.$ehash))
exit(json_encode(array('status'=>'ok','hash'=>$ehash,'filetype'=>$type,'url'=>URL.$ehash)));
if($ehash && file_exists(getDataDir().DS.$ehash.DS.$ehash))
exit(json_encode(array('status'=>'ok','hash'=>$ehash,'filetype'=>$type,'url'=>getURL().$ehash)));
//cross check filetype for controllers
//
@@ -74,17 +90,11 @@ if($answer['hash'] && $answer['status']=='ok')
if(getDeleteCodeOfHash($answer['hash']))
{
$answer['delete_code'] = getDeleteCodeOfHash($answer['hash']);
$answer['delete_url'] = URL.'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
$answer['delete_url'] = getURL().'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
}
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
(new $contr())->pushFile($answer['hash']);
}
storageControllerUpload($answer['hash']);
}
if($answer['hash'] && $answer['status']=='ok')
@@ -95,16 +105,10 @@ if($answer['hash'] && $answer['status']=='ok')
if(getDeleteCodeOfHash($answer['hash']))
{
$answer['delete_code'] = getDeleteCodeOfHash($answer['hash']);
$answer['delete_url'] = URL.'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
$answer['delete_url'] = getURL().'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
}
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
(new $contr())->pushFile($answer['hash']);
}
storageControllerUpload($answer['hash']);
}
echo json_encode($answer);

53
api/info.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
// basic path definitions
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).'/..');
header('Content-Type: application/json; charset=utf-8');
//loading default settings if exist
if(!file_exists(ROOT.DS.'inc'.DS.'config.inc.php'))
exit('Rename /inc/example.config.inc.php to /inc/config.inc.php first!');
include_once(ROOT.DS.'inc'.DS.'config.inc.php');
//loading core and controllers
include_once(ROOT . DS . 'inc' . DS. 'core.php');
//load external things if existing
if(file_exists(ROOT.'/lib/vendor/autoload.php'))
require ROOT.'/lib/vendor/autoload.php';
if($_REQUEST['ip']=='pls') exit(getUserIP());
loadAllContentControllers();
$hash = $_REQUEST['hash'];
if(!isExistingHash($hash))
{
exit(json_encode(array('status'=>'err','reason'=>'File not found')));
}
else
{
$answer = getInfoAboutHash($hash);
$answer['status'] = 'ok';
exit(json_encode($answer));
}
function getInfoAboutHash($hash)
{
$file = getDataDir().DS.$hash.DS.$hash;
if(!file_exists($file))
return array('status'=>'err','reason'=>'File not found');
$size = filesize($file);
$size_hr = renderSize($size);
$content_type = exec("file -bi " . escapeshellarg($file));
if($content_type && strpos($content_type,'/')!==false && strpos($content_type,';')!==false)
{
$type = $content_type;
$c = explode(';',$type);
$type = $c[0];
}
return array('hash'=>$hash,'size_bytes'=>$size,'size_interpreted'=>$size_hr,'type'=>$type,'type_interpreted'=>getTypeOfFile($file));
}

View File

@@ -10,10 +10,18 @@ include_once(ROOT.DS.'inc'.DS.'config.inc.php');
//loading core and controllers
include_once(ROOT . DS . 'inc' . DS. 'core.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'text'. DS . 'text.controller.php');
//load external things if existing
if(file_exists(ROOT.'/lib/vendor/autoload.php'))
require ROOT.'/lib/vendor/autoload.php';
$controllers = loadAllContentControllers();
if(!in_array('TextController',$controllers))
exit(json_encode(array('status'=>'err','reason'=>'Text controller not enabled')));
// check if client has permission to upload
executeUploadPermission();
// check write permissions first
if(!isFolderWritable(ROOT.DS.'data'))
if(!isFolderWritable(getDataDir()))
exit(json_encode(array('status'=>'err','reason'=>'Data directory not writable')));
else if(!isFolderWritable(ROOT.DS.'tmp'))
exit(json_encode(array('status'=>'err','reason'=>'Temp directory not writable')));
@@ -29,11 +37,11 @@ if($_REQUEST['api_paste_code'])
$sha1 = sha1_file($tmpfile);
$sha_hash = sha1Exists($sha1);
if($sha_hash)
exit(URL.$sha_hash);
exit(getURL().$sha_hash);
$answer = (new TextController())->handleUpload($tmpfile,$hash);
if($answer['hash'] && $answer['status']=='ok')
addSha1($answer['hash'],$sha1);
echo URL.$hash;
echo getURL().$hash;
}

View File

@@ -1,7 +1,9 @@
<?php
// basic path definitions
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).'/..');
define('ROOT', dirname(__FILE__).DS.'..');
header('Content-Type: application/json; charset=utf-8');
//loading default settings if exist
if(!file_exists(ROOT.DS.'inc'.DS.'config.inc.php'))
@@ -10,18 +12,24 @@ include_once(ROOT.DS.'inc'.DS.'config.inc.php');
//loading core and controllers
include_once(ROOT . DS . 'inc' . DS. 'core.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'image'. DS . 'image.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'text'. DS . 'text.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'url'. DS . 'url.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'video'. DS . 'video.controller.php');
//load external things if existing
if(file_exists(ROOT.'/lib/vendor/autoload.php'))
require ROOT.'/lib/vendor/autoload.php';
$allowedcontentcontrollers = loadAllContentControllers();
// check write permissions first
if(!isFolderWritable(ROOT.DS.'data'))
if(!isFolderWritable(getDataDir()))
exit(json_encode(array('status'=>'err','reason'=>'Data directory not writable')));
else if(!isFolderWritable(ROOT.DS.'tmp'))
exit(json_encode(array('status'=>'err','reason'=>'Temp directory not writable')));
$hash = sanatizeString(trim($_REQUEST['hash']))?sanatizeString(trim($_REQUEST['hash'])):false;
// check if client has permission to upload
executeUploadPermission();
if(isset($_REQUEST['hash']))
$hash = sanatizeString(trim($_REQUEST['hash']));
else
$hash = false;
// check for POST upload
if ($_FILES['file']["error"] == UPLOAD_ERR_OK)
@@ -32,16 +40,28 @@ if ($_FILES['file']["error"] == UPLOAD_ERR_OK)
//check for duplicates
$sha1 = sha1_file($_FILES['file']["tmp_name"]);
$ehash = sha1Exists($sha1);
if($ehash && file_exists(ROOT.DS.'data'.DS.$ehash.DS.$ehash))
exit(json_encode(array('status'=>'ok','hash'=>$ehash,'filetype'=>$type,'url'=>URL.$ehash)));
if($ehash && file_exists(getDataDir().DS.$ehash.DS.$ehash))
exit(json_encode(array('status'=>'ok','hash'=>$ehash,'filetype'=>$type,'url'=>getURL().$ehash)));
//cross check filetype for controllers
//
//image?
foreach($allowedcontentcontrollers as $cc)
{
if(in_array($type,(new $cc)->getRegisteredExtensions()))
{
$answer = (new $cc())->handleUpload($_FILES['file']['tmp_name'],$hash);
break;
}
}
/*
if(in_array($type,(new ImageController)->getRegisteredExtensions()))
{
$answer = (new ImageController())->handleUpload($_FILES['file']['tmp_name'],$hash);
}
//or, a text
else if($type=='text')
{
@@ -52,7 +72,7 @@ if ($_FILES['file']["error"] == UPLOAD_ERR_OK)
{
$answer = (new VideoController())->handleUpload($_FILES['file']['tmp_name'],$hash);
}
*/
if(!$answer)
$answer = array('status'=>'err','reason'=>'Unsupported filetype: '.$type,'filetype'=>$type);
@@ -65,17 +85,11 @@ if ($_FILES['file']["error"] == UPLOAD_ERR_OK)
if(getDeleteCodeOfHash($answer['hash']))
{
$answer['delete_code'] = getDeleteCodeOfHash($answer['hash']);
$answer['delete_url'] = URL.'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
$answer['delete_url'] = getURL().'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
}
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
(new $contr())->pushFile($answer['hash']);
}
storageControllerUpload($answer['hash']);
}
echo json_encode($answer);

Binary file not shown.

View File

@@ -0,0 +1,38 @@
<?php
use Bitverse\Identicon\Identicon;
use Bitverse\Identicon\Color\Color;
use Bitverse\Identicon\Generator\RingsGenerator;
use Bitverse\Identicon\Preprocessor\MD5Preprocessor;
class IdenticonController implements ContentController
{
public const ctype = 'dynamic';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('identicon');}
public function handleHash($hash,$url)
{
unset($url[array_search('identicon',$url)]);
$url = array_values($url);
$generator = new RingsGenerator();
$generator->setBackgroundColor(Color::parseHex('#EEEEEE'));
$identicon = new Identicon(new MD5Preprocessor(), $generator);
$icon = $identicon->getIcon($url[0]);
header('Content-type: image/svg+xml');
echo $icon;
}
public function handleUpload($tmpfile,$hash=false)
{
return array('status'=>'err','hash'=>$hash,'reason'=>'Cannot upload to Identicons');
}
}

View File

@@ -0,0 +1,278 @@
<?php
function getFilters()
{
return get_class_methods('Filter');
}
class Filter {
public function sepia($im,$val) {
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_COLORIZE, 100, 50, 0);
return $im;
}
public function sepia2($im,$val) {
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_BRIGHTNESS, -10);
imagefilter($im, IMG_FILTER_CONTRAST, -20);
imagefilter($im, IMG_FILTER_COLORIZE, 60, 30, -15);
return $im;
}
public function sharpen($im,$val) {
$gaussian = array(
array(1.0, 1.0, 1.0),
array(1.0, -7.0, 1.0),
array(1.0, 1.0, 1.0)
);
imageconvolution($im, $gaussian, 1, 4);
return $im;
}
public function emboss($im,$val) {
$gaussian = array(
array(-2.0, -1.0, 0.0),
array(-1.0, 1.0, 1.0),
array(0.0, 1.0, 2.0)
);
imageconvolution($im, $gaussian, 1, 5);
return $im;
}
public function cool($im,$val) {
imagefilter($im, IMG_FILTER_MEAN_REMOVAL);
imagefilter($im, IMG_FILTER_CONTRAST, -50);
return $im;
}
public function light($im,$val) {
imagefilter($im, IMG_FILTER_BRIGHTNESS, 10);
imagefilter($im, IMG_FILTER_COLORIZE, 100, 50, 0, 10);
return $im;
}
public function aqua($im,$val) {
imagefilter($im, IMG_FILTER_COLORIZE, 0, 70, 0, 30);
return $im;
}
public function fuzzy($im,$val) {
$gaussian = array(
array(1.0, 1.0, 1.0),
array(1.0, 1.0, 1.0),
array(1.0, 1.0, 1.0)
);
imageconvolution($im, $gaussian, 9, 20);
return $im;
}
public function boost($im,$val) {
imagefilter($im, IMG_FILTER_CONTRAST, -35);
imagefilter($im, IMG_FILTER_BRIGHTNESS, 10);
return $im;
}
public function boost2($im,$val) {
imagefilter( $im, IMG_FILTER_CONTRAST, -35);
imagefilter( $im, IMG_FILTER_COLORIZE, 25, 25, 25);
return $im;
}
public function gray($im,$val) {
imagefilter($im, IMG_FILTER_CONTRAST, -60);
imagefilter($im, IMG_FILTER_GRAYSCALE);
return $im;
}
public function antique($im,$val) {
imagefilter($im, IMG_FILTER_BRIGHTNESS, 0);
imagefilter($im, IMG_FILTER_CONTRAST, -30);
imagefilter($im, IMG_FILTER_COLORIZE, 75, 50, 25);
return $im;
}
public function blackwhite($im,$val) {
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_BRIGHTNESS, 10);
imagefilter($im, IMG_FILTER_CONTRAST, -20);
return $im;
}
public function vintage($im,$val) {
imagefilter($im, IMG_FILTER_BRIGHTNESS, 10);
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_COLORIZE, 40, 10, -15);
return $im;
}
public function concentrate($im,$val) {
imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR);
imagefilter($im, IMG_FILTER_SMOOTH, -10);
return $im;
}
public function hermajesty($im,$val) {
imagefilter($im, IMG_FILTER_BRIGHTNESS, -10);
imagefilter($im, IMG_FILTER_CONTRAST, -5);
imagefilter($im, IMG_FILTER_COLORIZE, 80, 0, 60);
return $im;
}
public function everglow($im,$val) {
imagefilter($im, IMG_FILTER_BRIGHTNESS, -30);
imagefilter($im, IMG_FILTER_CONTRAST, -5);
imagefilter($im, IMG_FILTER_COLORIZE, 30, 30, 0);
return $im;
}
public function freshblue($im,$val) {
imagefilter($im, IMG_FILTER_CONTRAST, -5);
imagefilter($im, IMG_FILTER_COLORIZE, 20, 0, 80, 60);
return $im;
}
public function tender($im,$val) {
imagefilter($im, IMG_FILTER_CONTRAST, 5);
imagefilter($im, IMG_FILTER_COLORIZE, 80, 20, 40, 50);
imagefilter($im, IMG_FILTER_COLORIZE, 0, 40, 40, 100);
imagefilter($im, IMG_FILTER_SELECTIVE_BLUR);
return $im;
}
public function dream($im,$val) {
imagefilter($im, IMG_FILTER_COLORIZE, 150, 0, 0, 50);
imagefilter($im, IMG_FILTER_NEGATE);
imagefilter($im, IMG_FILTER_COLORIZE, 0, 50, 0, 50);
imagefilter($im, IMG_FILTER_NEGATE);
imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR);
return $im;
}
public function frozen($im,$val) {
imagefilter($im, IMG_FILTER_BRIGHTNESS, -15);
imagefilter($im, IMG_FILTER_COLORIZE, 0, 0, 100, 50);
imagefilter($im, IMG_FILTER_COLORIZE, 0, 0, 100, 50);
imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR);
return $im;
}
public function forest($im,$val) {
imagefilter($im, IMG_FILTER_COLORIZE, 0, 0, 150, 50);
imagefilter($im, IMG_FILTER_NEGATE);
imagefilter($im, IMG_FILTER_COLORIZE, 0, 0, 150, 50);
imagefilter($im, IMG_FILTER_NEGATE);
imagefilter($im, IMG_FILTER_SMOOTH, 10);
return $im;
}
public function rain($im,$val) {
imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR);
imagefilter($im, IMG_FILTER_MEAN_REMOVAL);
imagefilter($im, IMG_FILTER_NEGATE);
imagefilter($im, IMG_FILTER_COLORIZE, 0, 80, 50, 50);
imagefilter($im, IMG_FILTER_NEGATE);
imagefilter($im, IMG_FILTER_SMOOTH, 10);
return $im;
}
public function orangepeel($im,$val) {
imagefilter($im, IMG_FILTER_COLORIZE, 100, 20, -50, 20);
imagefilter($im, IMG_FILTER_SMOOTH, 10);
imagefilter($im, IMG_FILTER_BRIGHTNESS, -10);
imagefilter($im, IMG_FILTER_CONTRAST, 10);
imagegammacorrect($im, 1, 1.2 );
return $im;
}
public function darken($im,$val) {
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_BRIGHTNESS, -50);
return $im;
}
public function summer($im,$val) {
imagefilter($im, IMG_FILTER_COLORIZE, 0, 150, 0, 50);
imagefilter($im, IMG_FILTER_NEGATE);
imagefilter($im, IMG_FILTER_COLORIZE, 25, 50, 0, 50);
imagefilter($im, IMG_FILTER_NEGATE);
return $im;
}
public function retro($im,$val) {
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_COLORIZE, 100, 25, 25, 50);
return $im;
}
public function country($im,$val) {
imagefilter($im, IMG_FILTER_BRIGHTNESS, -30);
imagefilter($im, IMG_FILTER_COLORIZE, 50, 50, 50, 50);
imagegammacorrect($im, 1, 0.3);
return $im;
}
public function washed($im,$val) {
imagefilter($im, IMG_FILTER_BRIGHTNESS, 30);
imagefilter($im, IMG_FILTER_NEGATE);
imagefilter($im, IMG_FILTER_COLORIZE, -50, 0, 20, 50);
imagefilter($im, IMG_FILTER_NEGATE );
imagefilter($im, IMG_FILTER_BRIGHTNESS, 10);
imagegammacorrect($im, 1, 1.2);
return $im;
}
public function pixelate($im,$val) {
if($val==null) $val = 10;
imagefilter($im,IMG_FILTER_PIXELATE,$val);
return $im;
}
public function blur($im,$blurFactor)
{
if(!$blurFactor)
$blurFactor = 3;
if($blurFactor>6)
$blurFactor = 6;
else if($blurFactor<0)
$blurFactor = 0;
// blurFactor has to be an integer
$blurFactor = round($blurFactor);
$originalWidth = imagesx($im);
$originalHeight = imagesy($im);
$smallestWidth = ceil($originalWidth * pow(0.5, $blurFactor));
$smallestHeight = ceil($originalHeight * pow(0.5, $blurFactor));
// for the first run, the previous image is the original input
$prevImage = $im;
$prevWidth = $originalWidth;
$prevHeight = $originalHeight;
// scale way down and gradually scale back up, blurring all the way
for($i = 0; $i < $blurFactor; $i += 1)
{
// determine dimensions of next image
$nextWidth = $smallestWidth * pow(2, $i);
$nextHeight = $smallestHeight * pow(2, $i);
// resize previous image to next size
$nextImage = imagecreatetruecolor($nextWidth, $nextHeight);
imagecopyresized($nextImage, $prevImage, 0, 0, 0, 0,
$nextWidth, $nextHeight, $prevWidth, $prevHeight);
// apply blur filter
imagefilter($nextImage, IMG_FILTER_GAUSSIAN_BLUR);
// now the new image becomes the previous image for the next step
$prevImage = $nextImage;
$prevWidth = $nextWidth;
$prevHeight = $nextHeight;
}
// scale back to original size and blur one more time
imagecopyresized($im, $nextImage,
0, 0, 0, 0, $originalWidth, $originalHeight, $nextWidth, $nextHeight);
imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR);
// clean up
imagedestroy($prevImage);
// return result
return $im;
}
}

View File

@@ -10,6 +10,7 @@
class ImageController implements ContentController
{
public const ctype = 'static';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('png','bmp','gif','jpg','jpeg','x-png','webp');}
@@ -24,8 +25,43 @@ class ImageController implements ContentController
case 17: $ext = 'ico';break; // ico
case 18: $ext = 'webp';break; // webp
case 2: //we clean up exif data of JPGs so GPS and other data is removed
case 2:
//we clean up exif data of JPGs so GPS and other data is removed
$res = imagecreatefromjpeg($tmpfile);
// rotate based on EXIF Orientation
$exif = exif_read_data($tmpfile);
if (!empty($exif['Orientation'])) {
switch ($exif['Orientation']) {
case 2:
imageflip($res, IMG_FLIP_HORIZONTAL);
case 1:
// Nothing to do
break;
case 4:
imageflip($res, IMG_FLIP_HORIZONTAL);
// Also rotate
case 3:
$res = imagerotate($res, 180, 0);
break;
case 5:
imageflip($res, IMG_FLIP_VERTICAL);
// Also rotate
case 6:
$res = imagerotate($res, -90, 0);
break;
case 7:
imageflip($res, IMG_FLIP_VERTICAL);
// Also rotate
case 8:
$res = imagerotate($res, 90, 0);
break;
}
}
imagejpeg($res, $tmpfile, (defined('JPEG_COMPRESSION')?JPEG_COMPRESSION:90));
$ext = 'jpg';
break;
@@ -48,12 +84,12 @@ class ImageController implements ContentController
storeFile($tmpfile,$hash,true);
return array('status'=>'ok','hash'=>$hash,'url'=>URL.$hash);
return array('status'=>'ok','hash'=>$hash,'url'=>getURL().$hash);
}
public function handleHash($hash,$url)
{
$path = ROOT.DS.'data'.DS.$hash.DS.$hash;
$path = getDataDir().DS.$hash.DS.$hash;
$type = getExtensionOfFilename($hash);
//get all our sub files where all the good functions lie
@@ -64,14 +100,31 @@ class ImageController implements ContentController
//don't do this if it's a gif because PHP can't handle animated gifs
if($type!='gif')
{
$filters = getFilters();
foreach($url as $u)
{
if(isSize($u))
$modifiers['size'] = $u;
else if(isRotation($u))
$modifiers['rotation'] = $u;
else // check for filters
{
foreach($filters as $filter)
{
if(startsWith($u,$filter) && ($u==$filter || startsWith($u,$filter.'_')))
{
$a = explode('_',$u);
$value = $a[1];
if(is_numeric($value))
$modifiers['filters'][] = array('filter'=>$filter,'value'=>$value);
else
$modifiers['filters'][] = array('filter'=>$filter);
}
}
}
}
if(in_array('webp',$url) && $type!='webp')
if( (in_array('webp',$url) && $type!='webp') || ( $this->shouldAlwaysBeWebp() && ($type=='jpg' || $type=='png') ) )
$modifiers['webp'] = true;
if(in_array('forcesize',$url) && $modifiers['size'])
$modifiers['forcesize'] = true;
@@ -89,8 +142,9 @@ class ImageController implements ContentController
//so if we take all parameters in key=>value form and hash it
//we get one nice little hash for every eventuality
$modhash = md5(http_build_query($modifiers,'',','));
$newpath = ROOT.DS.'data'.DS.$hash.DS.$modhash.'_'.$hash;
$newpath = getDataDir().DS.$hash.DS.$modhash.'_'.$hash;
$im = $this->getObjOfImage($path);
$f = new Filter();
if(!file_exists($newpath))
{
@@ -98,6 +152,15 @@ class ImageController implements ContentController
{
switch($mod)
{
case 'filters':
foreach($val as $fd)
{
$filter = $fd['filter'];
$value = $fd['value'];
$im = $f->$filter($im,$value);
}
break;
case 'size':
($modifiers['forcesize']?forceResize($im,$val):resize($im,$val));
break;
@@ -111,7 +174,7 @@ class ImageController implements ContentController
break;
case 'mp4':
$mp4path = ROOT.DS.'data'.DS.$hash.DS.$hash.'mp4';
$mp4path = getDataDir().DS.$hash.DS.$hash.'mp4';
if(!file_exists($mp4path))
$this->gifToMP4($path,$mp4path);
$path = $mp4path;
@@ -128,7 +191,7 @@ class ImageController implements ContentController
header ("Content-type: image/jpeg");
readfile($preview);
exit;
}
else if(in_array('download',$url))
{
@@ -148,6 +211,7 @@ class ImageController implements ContentController
{
$data = array('url'=>implode('/',$url),'hash'=>$hash,'filesize'=>renderSize(filesize($path)));
renderTemplate('video',$data);
exit;
}
break;
}
@@ -155,31 +219,46 @@ class ImageController implements ContentController
$this->saveObjOfImage($im,$newpath,$type);
}
else if($modifiers['webp'])
{
$type = 'webp';
}
$path = $newpath;
}
switch($type)
{
case 'jpeg':
case 'jpg':
header ("Content-type: image/jpeg");
header ("Last-Modified: ".gmdate('D, d M Y H:i:s ', filemtime($path)) . 'GMT');
header ("ETag: $hash");
header('Cache-control: public, max-age=31536000');
readfile($path);
break;
case 'png':
header ("Content-type: image/png");
header ("Last-Modified: ".gmdate('D, d M Y H:i:s ', filemtime($path)) . 'GMT');
header ("ETag: $hash");
header('Cache-control: public, max-age=31536000');
readfile($path);
break;
case 'gif':
header ("Content-type: image/gif");
header ("Last-Modified: ".gmdate('D, d M Y H:i:s ', filemtime($path)) . 'GMT');
header ("ETag: $hash");
header('Cache-control: public, max-age=31536000');
readfile($path);
break;
case 'webp':
header ("Content-type: image/webp");
header ("Last-Modified: ".gmdate('D, d M Y H:i:s ', filemtime($path)) . 'GMT');
header ("ETag: $hash");
header('Cache-control: public, max-age=31536000');
readfile($path);
break;
}
@@ -206,22 +285,46 @@ class ImageController implements ContentController
function saveObjOfImage($im,$path,$type)
{
$tmppath = '/tmp/'.getNewHash($type,12);
switch($type)
{
case 'jpeg':
case 'jpg':
imagejpeg($im,$path,(defined('JPEG_COMPRESSION')?JPEG_COMPRESSION:90));
imagejpeg($im,$tmppath,(defined('JPEG_COMPRESSION')?JPEG_COMPRESSION:90));
break;
case 'png':
imagepng($im,$path,(defined('PNG_COMPRESSION')?PNG_COMPRESSION:6));
imagepng($im,$tmppath,(defined('PNG_COMPRESSION')?PNG_COMPRESSION:6));
break;
case 'webp':
imagewebp($im,$path,(defined('WEBP_COMPRESSION')?WEBP_COMPRESSION:80));
imagepalettetotruecolor($im);
imagealphablending($im, true);
imagewebp($im,$tmppath,(defined('WEBP_COMPRESSION')?WEBP_COMPRESSION:80));
break;
}
return $im;
if(file_exists($tmppath) && filesize($tmppath)>0)
{
rename($tmppath,$path);
return $im;
}
else
{
return false;
}
}
function shouldAlwaysBeWebp()
{
//sanity check
if(!$_SERVER['HTTP_ACCEPT']) return false;
if(defined('ALWAYS_WEBP') && ALWAYS_WEBP && strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) !== false )
return true;
else
return false;
}
}

View File

@@ -91,14 +91,14 @@ function rotate(&$im,$direction)
if ($height > $width)
{
$ratio = $maxheight / $height;
$newheight = $maxheight;
$newwidth = $width * $ratio;
$newheight = intval($maxheight);
$newwidth = intval($width * $ratio) ;
}
else
{
$ratio = $maxwidth / $width;
$newwidth = $maxwidth;
$newheight = $height * $ratio;
$newwidth = intval($maxwidth) ;
$newheight = intval($height * $ratio);
}
$newimg = imagecreatetruecolor($newwidth,$newheight);

View File

@@ -0,0 +1,52 @@
<?php
class PlaceholderController implements ContentController
{
public const ctype = 'dynamic';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('placeholder');}
public function handleHash($hash,$url)
{
$path = getDataDir().DS.$hash.DS.$hash;
include_once(dirname(__FILE__).DS.'placeholdergenerator.php');
$pg = new PlaceholderGenerator();
foreach($url as $u)
{
if(isSize($u))
$modifiers['size'] = $u;
if(startsWith($u,'color-'))
{
$u = substr($u,6);
$colors = explode('-',$u);
foreach($colors as $c)
if(isColor($c))
$modifiers['colors'][] = (ctype_xdigit($c)?$c:color_name_to_hex($c));
if(count($modifiers['colors'])>4)
$modifiers['colors'] = array_slice($modifiers['colors'],0,4);
}
}
$img = $pg->generateImage($modifiers);
$img = $pg->gradient($img, $modifiers['colors']);
$img = $pg->addSizeText($img,$modifiers);
header ("Content-type: image/jpeg");
header ("ETag: $hash");
header('Cache-control: public, max-age=31536000');
imagejpeg($img,null,(defined('JPEG_COMPRESSION')?JPEG_COMPRESSION:90));
}
public function handleUpload($tmpfile,$hash=false)
{
return array('status'=>'err','hash'=>$hash,'reason'=>'Cannot upload to placeholder image');
}
}

View File

@@ -0,0 +1,88 @@
<?php
class PlaceholderGenerator {
function generateImage($modifiers)
{
$size = ($modifiers['size']?:'800x600');
$sd = sizeStringToWidthHeight($size);
$width = $sd['width'];
$height = $sd['height'];
$im = imagecreatetruecolor($width, $height);
return $im;
}
function addSizeText($im,$modifiers)
{
$size = imagesx($im).'x'.imagesy($im);
$text = $size;
//add the size as text in the center of the image
$textcolor = imagecolorallocate($im, 0, 0, 0);
$font = dirname(__FILE__).DS.'fonts/RonysiswadiArchitect5-1GErv.ttf';
//calculate the size of the text to make sure it will alway be visible
$fontsize = 20;
$textsize = imagettfbbox($fontsize, 0, $font, $text);
$scaleX = imagesx($im) / ($textsize[2] - $textsize[0] + 25);
$scaleY = imagesy($im) / ($textsize[1] - $textsize[7] + 25);
$scale = min($scaleX,$scaleY);
$fontsize = 20 * $scale;
$textsize = imagettfbbox($fontsize, 0, $font, $text);
$textwidth = $textsize[2] - $textsize[0];
$textheight = $textsize[1] - $textsize[7];
if($textwidth > imagesx($im) || $textheight > imagesy($im))
return $im;
$x = intval((imagesx($im) - $textwidth) / 2);
$y = intval((imagesy($im) - $textheight) / 2 + $textheight);
imagettftext($im, $fontsize, 0, $x, $y, $textcolor, $font, $text);
return $im;
}
function gradient($im, $c) {
$w = imagesx($im);
$h = imagesy($im);
if(!$c[0]) $c = ['ffffff','ffffff','ffffff','ffffff'];
else if(!$c[1]) $c = [$c[0],$c[0],$c[0],$c[0]];
else if(!$c[2]) $c = [$c[0],$c[0],$c[1],$c[1]];
else if(!$c[3]) $c = [$c[0],$c[1],$c[2],$c[0]];
for($i=0;$i<=3;$i++) {
$c[$i]=$this->hex2rgb($c[$i]);
}
$rgb=$c[0]; // start with top left color
for($x=0;$x<=$w;$x++) { // loop columns
for($y=0;$y<=$h;$y++) { // loop rows
// set pixel color
$col=imagecolorallocate($im,intval($rgb[0]),intval($rgb[1]),intval($rgb[2]));
imagesetpixel($im,$x-1,$y-1,$col);
// calculate new color
for($i=0;$i<=2;$i++) {
$rgb[$i]=
$c[0][$i]*(($w-$x)*($h-$y)/($w*$h)) +
$c[1][$i]*($x *($h-$y)/($w*$h)) +
$c[2][$i]*(($w-$x)*$y /($w*$h)) +
$c[3][$i]*($x *$y /($w*$h));
}
}
}
return $im;
}
function hex2rgb($hex)
{
$rgb[0]=hexdec(substr($hex,0,2));
$rgb[1]=hexdec(substr($hex,2,2));
$rgb[2]=hexdec(substr($hex,4,2));
return($rgb);
}
}

View File

@@ -2,12 +2,14 @@
class TextController implements ContentController
{
public const ctype = 'static';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('txt');}
public function getRegisteredExtensions(){return array('txt','text','csv');}
public function handleHash($hash,$url)
{
$path = ROOT.DS.'data'.DS.$hash.DS.$hash;
$path = getDataDir().DS.$hash.DS.$hash;
if(in_array('raw',$url))
{
@@ -48,11 +50,11 @@ class TextController implements ContentController
storeFile($tmpfile,$hash,true);
return array('status'=>'ok','hash'=>$hash,'url'=>URL.$hash);
return array('status'=>'ok','hash'=>$hash,'url'=>getURL().$hash);
}
function getTypeOfText($hash)
{
return file_get_contents(ROOT.DS.'data'.DS.$hash.DS.'type');
return file_get_contents(getDataDir().DS.$hash.DS.'type');
}
}

View File

@@ -2,6 +2,8 @@
class UrlController implements ContentController
{
public const ctype = 'static';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('url');}
public function handleHash($hash,$url){}

View File

@@ -2,12 +2,14 @@
class VideoController implements ContentController
{
public const ctype = 'static';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('mp4');}
public function handleHash($hash,$url)
{
$path = ROOT.DS.'data'.DS.$hash.DS.$hash;
$path = getDataDir().DS.$hash.DS.$hash;
//@todo: - resize by changing $path
@@ -19,7 +21,7 @@ class VideoController implements ContentController
{
$s = sizeStringToWidthHeight($size);
$width = $s['width'];
$newpath = ROOT.DS.'data'.DS.$hash.DS.$width.'_'.$hash;
$newpath = getDataDir().DS.$hash.DS.$width.'_'.$hash;
if(!file_exists($newpath))
$this->resize($path,$newpath,$width);
$path = $newpath;
@@ -72,12 +74,12 @@ class VideoController implements ContentController
return array('status'=>'err','hash'=>$hash,'reason'=>'Custom hash already exists');
}
storeFile($tmpfile,$hash,true);
$file = storeFile($tmpfile,$hash,true);
if(!$this->rightEncodedMP4($file))
system("nohup php ".ROOT.DS.'tools'.DS.'re-encode_mp4.php force '.$hash." > /dev/null 2> /dev/null &");
return array('status'=>'ok','hash'=>$hash,'url'=>URL.$hash);
return array('status'=>'ok','hash'=>$hash,'url'=>getURL().$hash);
}
@@ -91,6 +93,7 @@ class VideoController implements ContentController
$start = 0;
$end = $size - 1;
header('Content-type: video/mp4');
header('Cache-control: public, max-age=31536000');
header("Accept-Ranges: 0-$length");
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $start;

5
css/gh-fork-ribbon.min.css vendored Normal file
View File

@@ -0,0 +1,5 @@
/*!
* "Fork me on GitHub" CSS ribbon v0.2.3 | MIT License
* https://github.com/simonwhitaker/github-fork-ribbon-css
*/.github-fork-ribbon{width:12.1em;height:12.1em;position:absolute;overflow:hidden;top:0;right:0;z-index:9999;pointer-events:none;font-size:13px;text-decoration:none;text-indent:-999999px}.github-fork-ribbon.fixed{position:fixed}.github-fork-ribbon:active,.github-fork-ribbon:hover{background-color:rgba(0,0,0,0)}.github-fork-ribbon:after,.github-fork-ribbon:before{position:absolute;display:block;width:15.38em;height:1.54em;top:3.23em;right:-3.23em;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.github-fork-ribbon:before{content:"";padding:.38em 0;background-color:#a00;background-image:-webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,.15)));background-image:-webkit-linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,.15));background-image:-moz-linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,.15));background-image:-ms-linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,.15));background-image:-o-linear-gradient(top,rgba(0,0,0,0),rgba(0,0,0,.15));background-image:linear-gradient(to bottom,rgba(0,0,0,0),rgba(0,0,0,.15));-webkit-box-shadow:0 .15em .23em 0 rgba(0,0,0,.5);-moz-box-shadow:0 .15em .23em 0 rgba(0,0,0,.5);box-shadow:0 .15em .23em 0 rgba(0,0,0,.5);pointer-events:auto}.github-fork-ribbon:after{content:attr(data-ribbon);color:#fff;font:700 1em "Helvetica Neue",Helvetica,Arial,sans-serif;line-height:1.54em;text-decoration:none;text-shadow:0 -.08em rgba(0,0,0,.5);text-align:center;text-indent:0;padding:.15em 0;margin:.15em 0;border-width:.08em 0;border-style:dotted;border-color:#fff;border-color:rgba(255,255,255,.7)}.github-fork-ribbon.left-bottom,.github-fork-ribbon.left-top{right:auto;left:0}.github-fork-ribbon.left-bottom,.github-fork-ribbon.right-bottom{top:auto;bottom:0}.github-fork-ribbon.left-bottom:after,.github-fork-ribbon.left-bottom:before,.github-fork-ribbon.left-top:after,.github-fork-ribbon.left-top:before{right:auto;left:-3.23em}.github-fork-ribbon.left-bottom:after,.github-fork-ribbon.left-bottom:before,.github-fork-ribbon.right-bottom:after,.github-fork-ribbon.right-bottom:before{top:auto;bottom:3.23em}.github-fork-ribbon.left-top:after,.github-fork-ribbon.left-top:before,.github-fork-ribbon.right-bottom:after,.github-fork-ribbon.right-bottom:before{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}
/*# sourceMappingURL=gh-fork-ribbon.min.css.map */

1
css/imgs/logo/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2" viewBox="0 0 318.8 194"><circle cx="97" cy="97" r="97" style="fill:url(#a)"/><path d="M97.3 6.8c50 0 90.5 40.3 90.5 90s-40.5 90-90.5 90-90.5-40.3-90.5-90 40.5-90 90.5-90Zm69.8 140.7a85.2 85.2 0 0 0 16.6-50.6A86.2 86.2 0 0 0 97.3 11 86.2 86.2 0 0 0 11 96.9c0 17 5 32.8 13.5 46.2 36.3-67.4 36.2-74.7 45.3-74.7 8.8 0 10.3 7.6 43.3 71.4 16.7-25.9 18.9-30.4 24.3-30.4 6.7 0 8 5 29.7 38Zm-29.3-68.2a10.6 10.6 0 1 1 0 21.2 10.6 10.6 0 0 1 0-21.2Z" style="fill:url(#b)"/><path d="M253 37.1a33 33 0 1 1 10.4 27l-69.7 26.1-6.4-28.5L253 37.1Z" style="fill:url(#c)"/><path d="M263.4 129.3a32.9 32.9 0 0 1 55.4 24.2 33 33 0 0 1-65.8 3.2l-65.6-24.8 6.4-28.9 69.6 26.3Z" style="fill:url(#d)"/><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="matrix(79 0 0 -73 97.3 77.6)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#867278;stop-opacity:1"/><stop offset=".5" style="stop-color:#5c4955;stop-opacity:1"/><stop offset="1" style="stop-color:#433545;stop-opacity:1"/></radialGradient><radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="matrix(55 0 0 -55 253 48.6)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#867278;stop-opacity:1"/><stop offset=".5" style="stop-color:#5c4955;stop-opacity:1"/><stop offset="1" style="stop-color:#433545;stop-opacity:1"/></radialGradient><radialGradient id="d" cx="0" cy="0" r="1" gradientTransform="matrix(55 0 0 -56 253.1 144.8)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#867278;stop-opacity:1"/><stop offset=".5" style="stop-color:#5c4955;stop-opacity:1"/><stop offset="1" style="stop-color:#433545;stop-opacity:1"/></radialGradient><linearGradient id="b" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(97 0 0 97 53.6 97)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#40a1d4;stop-opacity:1"/><stop offset="1" style="stop-color:#2971c2;stop-opacity:1"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

69
docker/Dockerfile Normal file
View File

@@ -0,0 +1,69 @@
FROM alpine:3.18
RUN apk add --no-cache bash socat wget curl nginx file ffmpeg unzip zlib redis \
php82-fileinfo \
php82-session \
php \
php-curl \
php-openssl \
php-mbstring \
php-json \
php-gd \
php-dom \
php-fpm \
php82 \
php82-pdo \
php82-exif \
php82-curl \
php82-gd \
php82-json \
php82-phar \
php82-fpm \
php82-openssl \
php82-ctype \
php82-opcache \
php82-mbstring \
php82-sodium \
php82-xml \
php82-ftp \
php82-simplexml \
php82-session \
php82-fileinfo \
php82-pcntl \
php82-pecl-redis
RUN ln -s /usr/bin/php82 /usr/bin/php
RUN curl -sS https://getcomposer.org/installer | /usr/bin/php -- --install-dir=/usr/bin --filename=composer
RUN mkdir -p /var/www
WORKDIR /var/www
ADD . /var/www/.
ADD docker/rootfs/start.sh /etc/start.sh
RUN chmod +x /etc/start.sh
# Composer intall
WORKDIR /var/www/lib
RUN composer install --no-dev --no-interaction --no-progress --optimize-autoloader
# nginx stuff
WORKDIR /var/www
ADD docker/rootfs/nginx.conf /etc/nginx/http.d/default.conf
RUN mkdir -p /run/nginx
RUN mkdir -p /var/log/nginx
RUN sed -i 's/nobody/nginx/g' /etc/php82/php-fpm.d/www.conf
# Since requests can trigger conversion, let's give the server enough time to respond
RUN sed -i "/max_execution_time/c\max_execution_time=3600" /etc/php82/php.ini
RUN sed -i "/max_input_time/c\max_input_time=3600" /etc/php82/php.ini
WORKDIR /var/www/
# Volumes to mount
#VOLUME /var/lib/influxdb
VOLUME /var/www/data
EXPOSE 80
ENTRYPOINT ["/etc/start.sh"]

73
docker/rootfs/nginx.conf Normal file
View File

@@ -0,0 +1,73 @@
server {
listen 80 default_server;
set $base /var/www;
root /var/www/;
index index.php;
client_max_body_size 50M;
location / {
try_files $uri $uri/ /index.php?url=$request_uri;
}
location ~ /(data|tmp|bin|content-controllers|inc|interfaces|storage-controllers|templates|tools|docker) {
deny all;
return 404;
}
# logging
access_log /var/log/nginx/pictshare/access.log;
error_log /var/log/nginx/pictshare/error.log warn;
location ~ \.php$ {
# 404
try_files $fastcgi_script_name =404;
# default fastcgi_params
include fastcgi_params;
# fastcgi settings
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
fastcgi_max_temp_file_size 0; # caching files to disk while uploading. this will help low-memory devices
fastcgi_read_timeout 1d; # we set the timeout to 1 day so big uploads and have enough time to be delivered
fastcgi_send_timeout 1d; # it's especially important if you want to use storage controllers like S3 or FTP
fastcgi_request_buffering off; # disabbling buffering which will send the uploaded data right to PHP
# fastcgi params
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param PHP_ADMIN_VALUE "open_basedir=$base/:/usr/lib/php/:/tmp/";
}
location /favicon.ico {
log_not_found off;
}
# security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# svg, fonts
location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ {
add_header Access-Control-Allow-Origin "*";
expires 7d;
access_log off;
}
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
}

96
docker/rootfs/start.sh Normal file
View File

@@ -0,0 +1,96 @@
#!/bin/bash
######### functions
_maxUploadSize() {
echo "[i] Setting uploadsize to ${MAX_UPLOAD_SIZE}M"
sed -i "/post_max_size/c\post_max_size=${MAX_UPLOAD_SIZE}M" /etc/php82/php.ini
sed -i "/upload_max_filesize/c\upload_max_filesize=${MAX_UPLOAD_SIZE}M" /etc/php82/php.ini
# set error reporting no notices, no warnings
sed -i "/^error_reporting/c\error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_WARNING & ~E_NOTICE" /etc/php82/php.ini
sed -i -e "s/50M/${MAX_UPLOAD_SIZE}M/g" /etc/nginx/http.d/default.conf
MAX_RAM=$((MAX_UPLOAD_SIZE + 30)) # 30megs more than the upload size
echo "[i] Also changing memory limit of PHP to ${MAX_RAM}M"
sed -i -e "s/128M/${MAX_RAM}M/g" /etc/php82/php.ini
sed -i "/memory_limit/c\memory_limit=${MAX_RAM}M" /etc/php82/php.ini
}
_filePermissions() {
chown -R nginx:nginx /var/www
touch data/sha1.csv
chown nginx:nginx data/sha1.csv
}
_buildConfig() {
echo "<?php"
echo "define('URL', '${URL:-}');"
echo "define('TITLE', '${TITLE:-PictShare}');"
echo "define('ALLOWED_SUBNET', '${ALLOWED_SUBNET:-}');"
echo "define('CONTENTCONTROLLERS', '${CONTENTCONTROLLERS:-}');"
echo "define('MASTER_DELETE_CODE', '${MASTER_DELETE_CODE:-}');"
echo "define('MASTER_DELETE_IP', '${MASTER_DELETE_IP:-}');"
echo "define('UPLOAD_FORM_LOCATION', '${UPLOAD_FORM_LOCATION:-}');"
echo "define('UPLOAD_CODE', '${UPLOAD_CODE:-}');"
echo "define('LOG_UPLOADER', ${LOG_UPLOADER:-false});"
echo "define('MAX_RESIZED_IMAGES',${MAX_RESIZED_IMAGES:--1});"
echo "define('ALLOW_BLOATING', ${ALLOW_BLOATING:-false});"
echo "define('SHOW_ERRORS', ${SHOW_ERRORS:-false});"
echo "define('JPEG_COMPRESSION', ${JPEG_COMPRESSION:-90});"
echo "define('PNG_COMPRESSION', ${PNG_COMPRESSION:-6});"
echo "define('ALT_FOLDER', '${ALT_FOLDER:-}');"
echo "define('S3_BUCKET', '${S3_BUCKET:-}');"
echo "define('S3_ACCESS_KEY', '${S3_ACCESS_KEY:-}');"
echo "define('S3_SECRET_KEY', '${S3_SECRET_KEY:-}');"
echo "define('S3_ENDPOINT', '${S3_ENDPOINT:-}');"
echo "define('S3_REGION', '${S3_REGION:-}');"
echo "define('FTP_SERVER', '${FTP_SERVER:-}');"
echo "define('FTP_PORT', ${FTP_PORT:-21});"
echo "define('FTP_USER', '${FTP_USER:-}');"
echo "define('FTP_PASS', '${FTP_PASS:-}');"
echo "define('FTP_PASSIVEMODE', ${FTP_PASSIVEMODE:-true});"
echo "define('FTP_SSL', ${FTP_SSL:-false});"
echo "define('FTP_BASEDIR', '${FTP_BASEDIR:-}');"
echo "define('ENCRYPTION_KEY', '${ENCRYPTION_KEY:-}');"
echo "define('FFMPEG_BINARY', '${FFMPEG_BINARY:-/usr/bin/ffmpeg}');"
echo "define('ALWAYS_WEBP', ${ALWAYS_WEBP:-false});"
echo "define('ALLOWED_DOMAINS', '${ALLOWED_DOMAINS:-}');"
echo "define('SPLIT_DATA_DIR', ${SPLIT_DATA_DIR:-false});"
}
######### main
echo 'Starting Pictshare'
cd /var/www/
if [[ ${MAX_UPLOAD_SIZE:=100} =~ ^[0-9]+$ ]]; then
_maxUploadSize
fi
# run _filePermissions function unless SKIP_FILEPERMISSIONS is set to true
if [[ ${SKIP_FILEPERMISSIONS:=false} != true ]]; then
_filePermissions
fi
echo ' [+] Starting php'
php-fpm82
echo ' [+] Creating config'
_buildConfig > inc/config.inc.php
echo ' [+] Starting nginx'
mkdir -p /var/log/nginx/pictshare
touch /var/log/nginx/pictshare/access.log
touch /var/log/nginx/pictshare/error.log
nginx
tail -f /var/log/nginx/pictshare/*.log

View File

@@ -4,7 +4,7 @@ spl_autoload_register('autoload');
//disable output buffering
if (ob_get_level()) ob_end_clean();
error_reporting(E_ALL & ~E_NOTICE);
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
if(!defined('FFMPEG_BINARY'))
define('FFMPEG_BINARY',ROOT.DS.'bin'.DS.'ffmpeg');
@@ -24,7 +24,13 @@ function architect($url)
//just show the site
if( ( (!defined('UPLOAD_FORM_LOCATION') || (defined('UPLOAD_FORM_LOCATION') && !UPLOAD_FORM_LOCATION)) && count($u)==0) || (defined('UPLOAD_FORM_LOCATION') && UPLOAD_FORM_LOCATION && '/'.implode('/',$u)==UPLOAD_FORM_LOCATION) )
{
renderTemplate('main');
// check if client address is allowed
$forbidden = false;
if(defined('ALLOWED_SUBNET') && ALLOWED_SUBNET != '' && !isIPInRange( getUserIP(), ALLOWED_SUBNET ))
{
$forbidden = true;
}
renderTemplate('main',array('forbidden'=>$forbidden));
return;
}
@@ -43,18 +49,51 @@ function architect($url)
if(!$sc)
$sc = getStorageControllers();
foreach($sc as $contr)
{
{
$c = new $contr();
if($c->isEnabled()===true && $c->hashExists($el))
if($c->isEnabled()===true && $c->hashExists($el))
{
$c->pullFile($el);
$hash = $el;
break; // we brake here because we already have the file. no need to check other storage controllers
$c->pullFile($hash,ROOT.DS.'tmp'.DS.$hash);
if(!file_exists(ROOT.DS.'tmp'.DS.$hash)) continue;
storeFile(ROOT.DS.'tmp'.DS.$hash,$hash,true);
break; // we break here because we already have the file. no need to check other storage controllers
}
else if($c->isEnabled()===true && defined('ENCRYPTION_KEY') && ENCRYPTION_KEY !='' && $c->hashExists($el.'.enc')) //this is an encrypted file. Let's decrypt it
{
$hash = $el.'.enc';
$c->pullFile($hash,ROOT.DS.'tmp'.DS.$hash);
if(!file_exists(ROOT.DS.'tmp'.DS.$hash)) continue;
$enc = new Encryption;
$hash = substr($hash,0,-4);
$enc->decryptFile(ROOT.DS.'tmp'.DS.$el.'.enc', ROOT.DS.'tmp'.DS.$hash,base64_decode(ENCRYPTION_KEY));
storeFile(ROOT.DS.'tmp'.DS.$hash,$hash,true);
unlink(ROOT.DS.'tmp'.DS.$el.'.enc');
break; // we break here because we already have the file. no need to check other storage controllers
}
}
}
}
// if it's still false, we only have one hope: Maybe it's from a dynamic controller and the cache hasn't been created yet
else if($hash===false)
{
foreach(loadAllContentControllers(true) as $cc)
{
if((new $cc)::ctype=='dynamic' && in_array((new $cc)->getRegisteredExtensions()[0],$u) )
{
$hash = true;
break;
}
}
}
}
//we didn't find a hash. send error 404
if($hash===false)
{
@@ -85,38 +124,82 @@ function architect($url)
$extension = pathinfo($hash, PATHINFO_EXTENSION);
//First, check if URL is an image
if(in_array($extension,(new ImageController)->getRegisteredExtensions()))
foreach(loadAllContentControllers(true) as $cc)
{
(new ImageController())->handleHash($hash,$u);
}
//or, a url
else if(in_array($extension,(new UrlController)->getRegisteredExtensions()))
{
var_dump("Url");
}
//or, a text
else if(in_array($extension,(new TextController)->getRegisteredExtensions()))
{
(new TextController())->handleHash($hash,$u);
}
//or, a video
else if(in_array($extension,(new VideoController)->getRegisteredExtensions()))
{
(new VideoController())->handleHash($hash,$u);
}
//very odd. We know it's a valid hash but no controller says it's one of their kids
//oh well
else
{
var_dump("odd err");
if(
((new $cc)::ctype=='dynamic' && in_array((new $cc)->getRegisteredExtensions()[0],$u)) ||
((new $cc)::ctype=='static' && in_array($extension,(new $cc)->getRegisteredExtensions()))
)
{
(new $cc())->handleHash($hash,$u);
return;
}
}
http_response_code(404);
die("404");
}
//var_dump($u);
}
function storageControllerUpload($hash)
{
// Lets' check all storage controllers and tell them that a new file was uploaded
$sc = getStorageControllers();
$allgood = true;
$uploadedhash =$hash;
foreach($sc as $contr)
{
$controller = new $contr();
if($controller->isEnabled()===true)
{
$source = getDataDir().DS.$hash.DS.$hash;
if(defined('ENCRYPTION_KEY') && ENCRYPTION_KEY) //ok so we got an encryption key which means we'll store only the encrypted file
{
$enc = new Encryption;
$encoded_file = ROOT.DS.'tmp'.DS.$hash.'.enc';
$enc->encryptFile($source,$encoded_file,base64_decode(ENCRYPTION_KEY));
$controller->pushFile($encoded_file,$hash.'.enc');
unlink($encoded_file);
$uploadedhash = $hash.'.enc';
}
else // not encrypted
$controller->pushFile($source,$hash);
//let's check if the file is really there. If not, queue it for later
if(!$controller->hashExists($uploadedhash))
{
$allgood = false;
$queuefile=ROOT.DS.'tmp'.DS.'controllerqueue.txt';
if(!file_exists($queuefile) || !stringInFile($hash,$queuefile))
{
$fp=fopen($queuefile,'a');
if($fp)
{
fwrite($fp,$hash."\n");
fclose($fp);
}
}
}
}
}
return $allgood;
}
function stringInFile($string,$file)
{
$handle = fopen($file, 'r');
while (($line = fgets($handle)) !== false) {
$line=trim($line);
if($line==$string) return true;
}
fclose($handle);
return false;
}
function getNewHash($type,$length=10)
{
while(1)
@@ -129,7 +212,8 @@ function getNewHash($type,$length=10)
function isExistingHash($hash)
{
return is_dir(ROOT.DS.'data'.DS.$hash);
if(!trim($hash)) return false;
return is_dir(getDataDir().DS.$hash);
}
function mightBeAHash($string)
@@ -146,10 +230,10 @@ function mightBeAHash($string)
function autoload($className)
{
if (file_exists(ROOT . DS . 'content-controllers' . DS . strtolower($className) . '.php'))
require_once(ROOT . DS . 'content-controllers' . DS . strtolower($className) . '.php');
if (file_exists(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php'))
require_once(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php');
require_once(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php');
if ($className=='Encryption')
require_once(ROOT . DS . 'inc' . DS . 'encryption.php');
}
function renderTemplate($template,$vars=false)
@@ -275,16 +359,19 @@ function renderSize($byte)
function getTypeOfFile($url)
{
$fi = new finfo(FILEINFO_MIME);
$type = $fi->buffer(file_get_contents($url, false, null, -1, 1024));
// on linux use the "file" command or it will handle everything as octet-stream
if(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' && startsWith($type,'application/octet-stream'))
if(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')
{
$content_type = exec("file -bi " . escapeshellarg($url));
if($content_type && $content_type!=$type && strpos($content_type,'/')!==false && strpos($content_type,';')!==false)
if($content_type && strpos($content_type,'/')!==false && strpos($content_type,';')!==false)
$type = $content_type;
}
else
{
//for windows we'll use mime_content_type. Make sure you have enabled the "exif" extension in php.ini
$type = mime_content_type($url);
}
if(!$type) return false;
if(startsWith($type,'text')) return 'text';
$arr = explode(';', trim($type));
if(count($arr)>1)
@@ -298,9 +385,11 @@ function getTypeOfFile($url)
$type = $a2[1];
}
if($type=='octet-stream' && (new VideoController())->isProperMP4($url)) return 'mp4';
if($type=='mp4' && !(new VideoController())->isProperMP4($url))
return false;
if($type=='octet-stream')
if((new VideoController())->isProperMP4($url)) return 'mp4';
if($type=='mp4')
if(!(new VideoController())->isProperMP4($url))
return false;
return $type;
}
@@ -330,11 +419,13 @@ function endswith($string, $test) {
function getUserIP()
{
if(isCloudflare() || $_SERVER['HTTP_CF_CONNECTING_IP'])
return $_SERVER['HTTP_CF_CONNECTING_IP'];
$client = @$_SERVER['HTTP_CLIENT_IP'];
$forward = @$_SERVER['HTTP_X_FORWARDED_FOR'];
$remote = $_SERVER['REMOTE_ADDR'];
if(strpos($forward,','))
if($forward != null && strpos($forward,','))
{
$a = explode(',',$forward);
$forward = trim($a[0]);
@@ -357,10 +448,13 @@ function getUserIP()
// checks the list of uploaded files for this hash
function sha1Exists($sha1)
{
$handle = fopen(ROOT.DS.'data'.DS.'sha1.csv', "r");
$shafile = getDataDir().DS.'sha1.csv';
if(!file_exists($shafile)) touch($shafile);
$handle = fopen($shafile, "r");
if ($handle) {
while (($line = fgets($handle)) !== false) {
if(substr($line,0,40)==$sha1) return trim(substr($line,41));
if(substr($line,0,40)===$sha1) return trim(substr($line,41));
}
fclose($handle);
@@ -372,7 +466,7 @@ function sha1Exists($sha1)
function addSha1($hash,$sha1)
{
if(sha1Exists($sha1)) return;
$fp = fopen(ROOT.DS.'data'.DS.'sha1.csv','a');
$fp = fopen(getDataDir().DS.'sha1.csv','a');
fwrite($fp,"$sha1;$hash\n");
fclose($fp);
return true;
@@ -387,6 +481,176 @@ function isSize($var)
return true;
}
function isColor($var)
{
if(strlen($var)==6 && ctype_xdigit($var)) return true;
else
{
$col = color_name_to_hex($var);
if($col) return true;
else return false;
}
}
function color_name_to_hex($color_name)
{
// standard 147 HTML color names
$colors = array(
'aliceblue'=>'F0F8FF',
'antiquewhite'=>'FAEBD7',
'aqua'=>'00FFFF',
'aquamarine'=>'7FFFD4',
'azure'=>'F0FFFF',
'beige'=>'F5F5DC',
'bisque'=>'FFE4C4',
'black'=>'000000',
'blanchedalmond '=>'FFEBCD',
'blue'=>'0000FF',
'blueviolet'=>'8A2BE2',
'brown'=>'A52A2A',
'burlywood'=>'DEB887',
'cadetblue'=>'5F9EA0',
'chartreuse'=>'7FFF00',
'chocolate'=>'D2691E',
'coral'=>'FF7F50',
'cornflowerblue'=>'6495ED',
'cornsilk'=>'FFF8DC',
'crimson'=>'DC143C',
'cyan'=>'00FFFF',
'darkblue'=>'00008B',
'darkcyan'=>'008B8B',
'darkgoldenrod'=>'B8860B',
'darkgray'=>'A9A9A9',
'darkgreen'=>'006400',
'darkgrey'=>'A9A9A9',
'darkkhaki'=>'BDB76B',
'darkmagenta'=>'8B008B',
'darkolivegreen'=>'556B2F',
'darkorange'=>'FF8C00',
'darkorchid'=>'9932CC',
'darkred'=>'8B0000',
'darksalmon'=>'E9967A',
'darkseagreen'=>'8FBC8F',
'darkslateblue'=>'483D8B',
'darkslategray'=>'2F4F4F',
'darkslategrey'=>'2F4F4F',
'darkturquoise'=>'00CED1',
'darkviolet'=>'9400D3',
'deeppink'=>'FF1493',
'deepskyblue'=>'00BFFF',
'dimgray'=>'696969',
'dimgrey'=>'696969',
'dodgerblue'=>'1E90FF',
'firebrick'=>'B22222',
'floralwhite'=>'FFFAF0',
'forestgreen'=>'228B22',
'fuchsia'=>'FF00FF',
'gainsboro'=>'DCDCDC',
'ghostwhite'=>'F8F8FF',
'gold'=>'FFD700',
'goldenrod'=>'DAA520',
'gray'=>'808080',
'green'=>'008000',
'greenyellow'=>'ADFF2F',
'grey'=>'808080',
'honeydew'=>'F0FFF0',
'hotpink'=>'FF69B4',
'indianred'=>'CD5C5C',
'indigo'=>'4B0082',
'ivory'=>'FFFFF0',
'khaki'=>'F0E68C',
'lavender'=>'E6E6FA',
'lavenderblush'=>'FFF0F5',
'lawngreen'=>'7CFC00',
'lemonchiffon'=>'FFFACD',
'lightblue'=>'ADD8E6',
'lightcoral'=>'F08080',
'lightcyan'=>'E0FFFF',
'lightgoldenrodyellow'=>'FAFAD2',
'lightgray'=>'D3D3D3',
'lightgreen'=>'90EE90',
'lightgrey'=>'D3D3D3',
'lightpink'=>'FFB6C1',
'lightsalmon'=>'FFA07A',
'lightseagreen'=>'20B2AA',
'lightskyblue'=>'87CEFA',
'lightslategray'=>'778899',
'lightslategrey'=>'778899',
'lightsteelblue'=>'B0C4DE',
'lightyellow'=>'FFFFE0',
'lime'=>'00FF00',
'limegreen'=>'32CD32',
'linen'=>'FAF0E6',
'magenta'=>'FF00FF',
'maroon'=>'800000',
'mediumaquamarine'=>'66CDAA',
'mediumblue'=>'0000CD',
'mediumorchid'=>'BA55D3',
'mediumpurple'=>'9370D0',
'mediumseagreen'=>'3CB371',
'mediumslateblue'=>'7B68EE',
'mediumspringgreen'=>'00FA9A',
'mediumturquoise'=>'48D1CC',
'mediumvioletred'=>'C71585',
'midnightblue'=>'191970',
'mintcream'=>'F5FFFA',
'mistyrose'=>'FFE4E1',
'moccasin'=>'FFE4B5',
'navajowhite'=>'FFDEAD',
'navy'=>'000080',
'oldlace'=>'FDF5E6',
'olive'=>'808000',
'olivedrab'=>'6B8E23',
'orange'=>'FFA500',
'orangered'=>'FF4500',
'orchid'=>'DA70D6',
'palegoldenrod'=>'EEE8AA',
'palegreen'=>'98FB98',
'paleturquoise'=>'AFEEEE',
'palevioletred'=>'DB7093',
'papayawhip'=>'FFEFD5',
'peachpuff'=>'FFDAB9',
'peru'=>'CD853F',
'pink'=>'FFC0CB',
'plum'=>'DDA0DD',
'powderblue'=>'B0E0E6',
'purple'=>'800080',
'red'=>'FF0000',
'rosybrown'=>'BC8F8F',
'royalblue'=>'4169E1',
'saddlebrown'=>'8B4513',
'salmon'=>'FA8072',
'sandybrown'=>'F4A460',
'seagreen'=>'2E8B57',
'seashell'=>'FFF5EE',
'sienna'=>'A0522D',
'silver'=>'C0C0C0',
'skyblue'=>'87CEEB',
'slateblue'=>'6A5ACD',
'slategray'=>'708090',
'slategrey'=>'708090',
'snow'=>'FFFAFA',
'springgreen'=>'00FF7F',
'steelblue'=>'4682B4',
'tan'=>'D2B48C',
'teal'=>'008080',
'thistle'=>'D8BFD8',
'tomato'=>'FF6347',
'turquoise'=>'40E0D0',
'violet'=>'EE82EE',
'wheat'=>'F5DEB3',
'white'=>'FFFFFF',
'whitesmoke'=>'F5F5F5',
'yellow'=>'FFFF00',
'yellowgreen'=>'9ACD32');
$color_name = strtolower($color_name);
if (isset($colors[$color_name]))
return $colors[$color_name];
else
return false;
}
function getStorageControllers()
{
$controllers = array();
@@ -406,13 +670,18 @@ function getStorageControllers()
return $controllers;
}
function getAllContentControllers()
function loadAllContentControllers($all=false)
{
$allowedcontrollers = false;
if(defined('CONTENTCONTROLLERS') && CONTENTCONTROLLERS != '' && $all!==true)
{
$allowedcontrollers = array_map('strtolower', explode(',',CONTENTCONTROLLERS));
}
$controllers = array();
if ($handle = opendir(ROOT.DS.'content-controllers')) {
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != "..") {
if(is_dir(ROOT.DS.'content-controllers'.DS.$entry) && file_exists(ROOT.DS.'content-controllers'.DS.$entry.DS."$entry.controller.php"))
if(is_dir(ROOT.DS.'content-controllers'.DS.$entry) && file_exists(ROOT.DS.'content-controllers'.DS.$entry.DS."$entry.controller.php") && ( ($allowedcontrollers!==false && in_array($entry,$allowedcontrollers) ) || $allowedcontrollers===false))
{
$controllers[] = ucfirst($entry).'Controller';
include_once(ROOT.DS.'content-controllers'.DS.$entry.DS."$entry.controller.php");
@@ -428,16 +697,19 @@ function getAllContentControllers()
function getAllContentFiletypes()
{
$types = array();
$controllers = getAllContentControllers(true);
$controllers = loadAllContentControllers();
foreach($controllers as $c)
{
$types = array_merge($types,(new $c)->getRegisteredExtensions());
$instance = new $c;
if($instance::ctype=='static')
$types = array_merge($types,(new $instance)->getRegisteredExtensions());
}
return $types;
}
function rrmdir($dir) {
chmod($dir, 0777);
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
@@ -445,7 +717,9 @@ function rrmdir($dir) {
if (is_dir($dir."/".$object))
rrmdir($dir."/".$object);
else
{
unlink($dir."/".$object);
}
}
}
rmdir($dir);
@@ -454,9 +728,9 @@ function rrmdir($dir) {
function storeFile($srcfile,$hash,$deleteoriginal=false)
{
if(is_dir(ROOT.DS.'data'.DS.$hash) && file_exists(ROOT.DS.'data'.DS.$hash.DS.$hash)) return;
mkdir(ROOT.DS.'data'.DS.$hash);
$file = ROOT.DS.'data'.DS.$hash.DS.$hash;
if(is_dir(getDataDir().DS.$hash) && file_exists(getDataDir().DS.$hash.DS.$hash)) return;
mkdir(getDataDir().DS.$hash);
$file = getDataDir().DS.$hash.DS.$hash;
copy($srcfile, $file);
if($deleteoriginal===true)
@@ -466,36 +740,266 @@ function storeFile($srcfile,$hash,$deleteoriginal=false)
//creating a delete code
$deletecode = getRandomString(32);
$fh = fopen(ROOT.DS.'data'.DS.$hash.DS.'deletecode', 'w');
$fh = fopen(getDataDir().DS.$hash.DS.'deletecode', 'w');
fwrite($fh, $deletecode);
fclose($fh);
if(defined('LOG_UPLOADER') && LOG_UPLOADER)
{
$fh = fopen(ROOT.DS.'data'.DS.'uploads.csv', 'a');
fwrite($fh, time().';'.$url.';'.$hash.';'.getUserIP()."\n");
$fh = fopen(getDataDir().DS.'uploads.csv', 'a');
fwrite($fh, time().';;'.$hash.';'.getUserIP()."\n");
fclose($fh);
}
return $file;
}
function getDeleteCodeOfHash($hash)
{
return file_get_contents(ROOT.DS.'data'.DS.$hash.DS.'deletecode');
if(file_exists(getDataDir().DS.$hash.DS.'deletecode'))
return file_get_contents(getDataDir().DS.$hash.DS.'deletecode');
return false;
}
function deleteHash($hash)
{
//delete all local images
rrmdir(ROOT.DS.'data'.DS.$hash);
//@todo: add hash to deleted list. also on all controllers
//delete all files in directory
rrmdir(getDataDir().DS.$hash);
//tell every storage controller to delete theirs as well
$sc = getStorageControllers();
foreach($sc as $contr)
{
$c = new $contr();
if($c->isEnabled()===true && $c->hashExists($el))
if($c->isEnabled()===true && $c->hashExists($hash))
{
$c->deleteFile($el);
$c->deleteFile($hash);
}
//delete encrypted file if it exists
if($c->isEnabled()===true && defined('ENCRYPTION_KEY') && ENCRYPTION_KEY !='' && $c->hashExists($hash.'.enc'))
{
$c->deleteFile($hash.'.enc');
}
}
}
}
/**
* Check if a given IPv4 or IPv6 is in a network
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, or 2001:db8::8a2e:370:7334/128
* @return boolean true if the ip is in this range / false if not.
* via https://stackoverflow.com/a/56050595/1174516
*/
function isIPInRange( $ip, $range ) {
if(strpos($range,',')!==false)
{
// we got a list of ranges. splitting
$ranges = array_map('trim',explode(',',$range));
foreach($ranges as $range)
if(isIPInRange($ip,$range)) return true;
return false;
}
// Get mask bits
list($net, $maskBits) = explode('/', $range);
// Size
$size = (strpos($ip, ':') === false) ? 4 : 16;
// Convert to binary
$ip = inet_pton($ip);
$net = inet_pton($net);
if (!$ip || !$net) {
throw new InvalidArgumentException('Invalid IP address');
}
// Build mask
$solid = floor($maskBits / 8);
$solidBits = $solid * 8;
$mask = str_repeat(chr(255), $solid);
for ($i = $solidBits; $i < $maskBits; $i += 8) {
$bits = max(0, min(8, $maskBits - $i));
$mask .= chr((pow(2, $bits) - 1) << (8 - $bits));
}
$mask = str_pad($mask, $size, chr(0));
// Compare the mask
return ($ip & $mask) === ($net & $mask);
}
function loadContentControllers()
{
if(defined('CONTENTCONTROLLERS') && CONTENTCONTROLLERS != '')
{
$controllers = explode(',',CONTENTCONTROLLERS);
foreach($controllers as $controller)
{
$controller = strtolower($controller);
if(@file_exists(ROOT . DS . 'content-controllers' . DS. $controller. DS . $controller.'.controller.php'))
require_once(ROOT . DS . 'content-controllers' . DS. $controller. DS . $controller.'.controller.php');
}
}
else
loadAllContentControllers();
}
function ip_in_range($ip, $range) {
if (strpos($range, '/') == false)
$range .= '/32';
// $range is in IP/CIDR format eg 127.0.0.1/24
list($range, $netmask) = explode('/', $range, 2);
$range_decimal = ip2long($range);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, (32 - $netmask)) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return (($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal));
}
// from https://www.cloudflare.com/ips-v4
// and https://www.cloudflare.com/ips-v6
function _cloudflare_CheckIP($ip) {
$cf_ips = array_filter(array_map('trim',explode("\n","
173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/13
104.24.0.0/14
172.64.0.0/13
131.0.72.0/22
2400:cb00::/32
2606:4700::/32
2803:f800::/32
2405:b500::/32
2405:8100::/32
2a06:98c0::/29
2c0f:f248::/32
")));
$is_cf_ip = false;
foreach ($cf_ips as $cf_ip) {
if (ip_in_range($ip, $cf_ip)) {
$is_cf_ip = true;
break;
}
} return $is_cf_ip;
}
function _cloudflare_Requests_Check() {
$flag = true;
if(!isset($_SERVER['HTTP_CF_CONNECTING_IP'])) $flag = false;
if(!isset($_SERVER['HTTP_CF_IPCOUNTRY'])) $flag = false;
if(!isset($_SERVER['HTTP_CF_RAY'])) $flag = false;
if(!isset($_SERVER['HTTP_CF_VISITOR'])) $flag = false;
return $flag;
}
function isCloudflare() {
$ipCheck = _cloudflare_CheckIP($_SERVER['REMOTE_ADDR']);
$requestCheck = _cloudflare_Requests_Check();
return ($ipCheck && $requestCheck);
}
function executeUploadPermission()
{
if(defined('ALLOWED_SUBNET') && ALLOWED_SUBNET != '' && !isIPInRange( getUserIP(), ALLOWED_SUBNET ))
{
http_response_code(403);
exit(json_encode(array('status'=>'err','reason'=> 'Access denied')));
}
else if(defined('UPLOAD_CODE') && UPLOAD_CODE!='')
{
if(!isset($_REQUEST['uploadcode']) || $_REQUEST['uploadcode']!=UPLOAD_CODE)
{
http_response_code(403);
exit(json_encode(array('status'=>'err','reason'=> 'Incorrect upload code specified - Access denied')));
}
}
}
/**
* Checks if a URL is valid
* @param string $url
* @return boolean (true if valid, false if not)
*/
function checkURLForPrivateIPRange($url)
{
$host = getHost($url);
$ip = gethostbyname($host);
if(is_public_ipv4($ip) || is_public_ipv6($ip)) return false;
return true;
}
function getHost($url){
$URIs = parse_url(trim($url));
$host = !empty($URIs['host'])? $URIs['host'] : explode('/', $URIs['path'])[0];
return $host;
}
function is_public_ipv4($ip=NULL)
{
return filter_var(
$ip,
FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) === $ip ? TRUE : FALSE;
}
function is_public_ipv6($ip=NULL)
{
return filter_var(
$ip,
FILTER_VALIDATE_IP,
FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) === $ip ? TRUE : FALSE;
}
function getDataDir()
{
if(defined('SPLIT_DATA_DIR') && SPLIT_DATA_DIR===true && getDomain() && in_array(getDomain(),explode(',',ALLOWED_DOMAINS)))
{
$dir = ROOT.DS.'data'.DS.getDomain();
if(!is_dir($dir)) mkdir($dir);
return $dir;
}
return ROOT.DS.'data';
}
function getDomain($stripport=true)
{
$host = $_SERVER['HTTP_HOST'];
//strip port
if(strpos($host,':')!==false)
$strippedhost = substr($host,0,strpos($host,':'));
else $strippedhost = $host;
//check if it's in ALLOWED_DOMAINS
if(defined('ALLOWED_DOMAINS') && ALLOWED_DOMAINS!='')
{
$domains = explode(',',ALLOWED_DOMAINS);
if(!in_array($strippedhost,$domains)) //always check without port
return false;
else return ($stripport ? $strippedhost : $host);
}
else return false;
}
function getURL()
{
if(defined('URL') && URL !='')
return URL;
$protocol = strpos(strtolower($_SERVER['SERVER_PROTOCOL']), 'https') === FALSE ? 'http' : 'https';
return $protocol . '://' . getDomain(false).'/';
}

85
inc/encryption.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
class Encryption{
/**
* $key must have been generated at some point with: random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES)
*/
function encryptText($text,$key)
{
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_secretbox($text, $nonce, $key);
$encoded = base64_encode($nonce . $ciphertext);
return $encoded;
}
/**
* $key must have been generated at some point with: random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES)
*/
function decryptText($encoded,$key)
{
$decoded = base64_decode($encoded);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
return $plaintext;
}
/**
* $key must have been generated at some point with: sodium_crypto_secretstream_xchacha20poly1305_keygen();
*/
function encryptFile($infile,$enc_outfile,$key)
{
$chunk_size = 4096;
$fd_in = fopen($infile, 'rb');
$fd_out = fopen($enc_outfile, 'wb');
list($stream, $header) = sodium_crypto_secretstream_xchacha20poly1305_init_push($key);
fwrite($fd_out, $header);
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
do {
$chunk = fread($fd_in, $chunk_size);
if (feof($fd_in)) {
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL;
}
$encrypted_chunk = sodium_crypto_secretstream_xchacha20poly1305_push($stream, $chunk, '', $tag);
fwrite($fd_out, $encrypted_chunk);
} while ($tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
fclose($fd_out);
fclose($fd_in);
}
/**
* $key must have been generated at some point with: sodium_crypto_secretstream_xchacha20poly1305_keygen();
*/
function decryptFile($enc_infile,$outfile,$key)
{
$fd_in = fopen($enc_infile, 'rb');
$fd_out = fopen($outfile, 'wb');
$chunk_size = 4096;
$header = fread($fd_in, SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES);
$stream = sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $key);
do {
$chunk = fread($fd_in, $chunk_size + SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
list($decrypted_chunk, $tag) = sodium_crypto_secretstream_xchacha20poly1305_pull($stream, $chunk);
fwrite($fd_out, $decrypted_chunk);
} while (!feof($fd_in) && $tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
$ok = feof($fd_in);
fclose($fd_out);
fclose($fd_in);
if (!$ok) {
die('Invalid/corrupted input');
}
}
}

View File

@@ -2,6 +2,9 @@
/**
* All settings that are uncommented are mandatory
* Others optional
*
* For all possible config options, check https://github.com/HaschekSolutions/pictshare/blob/master/rtfm/CONFIG.md
* or /rtfm/CONFIG.md in your installation
*/
//Use a specific domain for links presented to the user
@@ -11,4 +14,12 @@ define('URL','https://dev.pictshare.net/');
//define('JPEG_COMPRESSION', 90);
//define('FFMPEG_BINARY','');
//define('ALT_FOLDER','/ftp/pictshare');
//define('ALT_FOLDER','/ftp/pictshare');
//define('ALLOWED_SUBNET','192.168.0.0/24');
//S3 settings
//
//define('S3_BUCKET','bucketname');
//define('S3_ACCESS_KEY','');
//define('S3_SECRET_KEY','');
//define('S3_ENDPOINT','http://localhost:9000'); //optional, only if you're using S3 compatible storage like Minio

View File

@@ -10,12 +10,13 @@ include_once(ROOT.DS.'inc'.DS.'config.inc.php');
//loading core and controllers
include_once(ROOT.DS.'inc'.DS.'core.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'image'. DS . 'image.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'text'. DS . 'text.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'url'. DS . 'url.controller.php');
require_once(ROOT . DS . 'content-controllers' . DS. 'video'. DS . 'video.controller.php');
loadAllContentControllers();
//load external things if existing
if(file_exists(ROOT.'/lib/vendor/autoload.php'))
require ROOT.'/lib/vendor/autoload.php';
//send the URL to the architect. It'll know what to do
$url = $_GET['url'];
$url = $_GET['url']?$_GET['url']:ltrim($_SERVER['REQUEST_URI'],'/');
architect($url);

View File

@@ -6,6 +6,14 @@
interface ContentController
{
/**
* When implementing a new content controller, you must specify a constant with the name of "ctype"
* This constant will be used to distinguish static controllers (which have files on disk) from dynamic ones (which don't, like the placeholder controller)
*
* Possible values: 'static' or 'dynamic'
*/
//const ctype = 'static';
/** This method will return all file extensions that will be associated with this content type
* for example 'pdf' but it could be anything really. You just need a way later to confirm that a type is what it says it is
*

View File

@@ -26,6 +26,14 @@ interface StorageController
*/
function hashExists($hash);
/**
* Returns an array of all items in this storage controller
*
* @return array
*/
function getItems();
/**
* If a file does exist in this storage system, then this method should
* get the file and put it in the default data directory
@@ -34,20 +42,21 @@ interface StorageController
* a folder that you might have to create first before putting the file in
*
* @param string $hash is the hash of the file that should be pulled from this storage system
* @param string $location is the location where the downloaded file should be placed
*
* @return bool true if successful
*/
function pullFile($hash);
function pullFile($hash,$location);
/**
* Whenever a new file is uploaded this method will be called
* You should then upload it or do whatever your storage system is meant to do with new files
*
* @param string $hash is the hash of the new file. The file path of this file is always ROOT.DS.'data'.DS.$hash.DS.$hash
* @param string $hash is the hash of the new file. The file path of this file is always getDataDir().DS.$hash.DS.$hash
*
* @return bool true if successful
*/
function pushFile($hash);
function pushFile($source,$hash);
/**
* If deletion of a file is requested, this method is called

View File

@@ -1,19 +1,40 @@
Dropzone.autoDiscover = false;
$(function() {
$(function () {
var myDropzone = new Dropzone("#dropzone");
myDropzone.on("success", function(file,response) {
console.log("raw response: "+response);
if(response==null || response =="null")
$("#uploadinfo").append("<div class='alert alert-danger' role='alert'><strong>Error uploading "+file.name+"</strong><br/>Reason is unknown :(</div>")
else
{
var o = JSON.parse(response);
if(o.status=='ok')
$("#uploadinfo").append("<div class='alert alert-success' role='alert'><strong>"+file.name+"</strong> uploaded as <a target='_blank' href='/"+o.hash+"'>"+o.hash+"</a><br/>URL: <a target='_blank' href='"+o.url+"'>"+o.url+"</a></div>")
else if(o.status=='err')
$("#uploadinfo").append("<div class='alert alert-danger' role='alert'><strong>Error uploading "+file.name+"</strong><br/>Reason: "+o.reason+"</div>")
console.log(o)
//console.log(myDropzone.options);
if (maxUploadFileSize !== undefined)
myDropzone.options.maxFilesize = maxUploadFileSize;
myDropzone.options.timeout = 0,
myDropzone.on("sending", function(file, xhr, formData) {
if(document.getElementById("uploadcode"))
formData.append("uploadcode", document.getElementById("uploadcode").value);
});
myDropzone.on('error', function(file, response) {
alert("Error: "+response.reason);
});
myDropzone.on("success", function (file, response) {
console.log("raw response: " + response);
if (response == null || response == "null")
$("#uploadinfo").append("<div class='alert alert-danger' role='alert'><strong>Error uploading " + file.name + "</strong><br/>Reason is unknown :(</div>")
else {
var o = response;
if (o.status == 'ok')
$("#uploadinfo").append("<div class='alert alert-success' role='alert'><strong>" + file.name + "</strong> uploaded as <a target='_blank' href='/" + o.hash + "'>" + o.hash + "</a><br/>URL: <a target='_blank' href='" + o.url + "'>" + o.url + "</a> <button class='btn btn-xs' onClick='navigator.clipboard.writeText(\"" + o.url + "\");'>Copy URL</button></div>")
else if (o.status == 'err')
$("#uploadinfo").append("<div class='alert alert-danger' role='alert'><strong>Error uploading " + file.name + "</strong><br/>Reason: " + o.reason + "</div>")
console.log(o)
}
});
document.onpaste = function (event) {
var items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (index in items) {
var item = items[index];
if (item.kind === 'file') {
// adds the file to your dropzone instance
myDropzone.addFile(item.getAsFile())
}
}
});
})
}
})

6
lib/composer.json Normal file
View File

@@ -0,0 +1,6 @@
{
"require": {
"aws/aws-sdk-php": "^3.33",
"bitverse/identicon": "^1.1"
}
}

956
lib/composer.lock generated Normal file
View File

@@ -0,0 +1,956 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "55774a7c38a82a511932ca4321acb7f8",
"packages": [
{
"name": "aws/aws-crt-php",
"version": "v1.2.4",
"source": {
"type": "git",
"url": "https://github.com/awslabs/aws-crt-php.git",
"reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/eb0c6e4e142224a10b08f49ebf87f32611d162b2",
"reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35||^5.6.3||^9.5",
"yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality."
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "AWS SDK Common Runtime Team",
"email": "aws-sdk-common-runtime@amazon.com"
}
],
"description": "AWS Common Runtime for PHP",
"homepage": "https://github.com/awslabs/aws-crt-php",
"keywords": [
"amazon",
"aws",
"crt",
"sdk"
],
"support": {
"issues": "https://github.com/awslabs/aws-crt-php/issues",
"source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.4"
},
"time": "2023-11-08T00:42:13+00:00"
},
{
"name": "aws/aws-sdk-php",
"version": "3.288.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "a1dfa12c7165de0b731ae8074c4ba1f3ae733f89"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a1dfa12c7165de0b731ae8074c4ba1f3ae733f89",
"reference": "a1dfa12c7165de0b731ae8074c4ba1f3ae733f89",
"shasum": ""
},
"require": {
"aws/aws-crt-php": "^1.2.3",
"ext-json": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"guzzlehttp/guzzle": "^6.5.8 || ^7.4.5",
"guzzlehttp/promises": "^1.4.0 || ^2.0",
"guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
"mtdowling/jmespath.php": "^2.6",
"php": ">=7.2.5",
"psr/http-message": "^1.0 || ^2.0"
},
"require-dev": {
"andrewsville/php-token-reflection": "^1.4",
"aws/aws-php-sns-message-validator": "~1.0",
"behat/behat": "~3.0",
"composer/composer": "^1.10.22",
"dms/phpunit-arraysubset-asserts": "^0.4.0",
"doctrine/cache": "~1.4",
"ext-dom": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-sockets": "*",
"nette/neon": "^2.3",
"paragonie/random_compat": ">= 2",
"phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5",
"psr/cache": "^1.0",
"psr/simple-cache": "^1.0",
"sebastian/comparator": "^1.2.3 || ^4.0",
"yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
"doctrine/cache": "To use the DoctrineCacheAdapter",
"ext-curl": "To send requests using cURL",
"ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages",
"ext-sockets": "To use client-side monitoring"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Aws\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Amazon Web Services",
"homepage": "http://aws.amazon.com"
}
],
"description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
"homepage": "http://aws.amazon.com/sdkforphp",
"keywords": [
"amazon",
"aws",
"cloud",
"dynamodb",
"ec2",
"glacier",
"s3",
"sdk"
],
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.288.1"
},
"time": "2023-11-22T19:35:38+00:00"
},
{
"name": "bitverse/identicon",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/bitverseio/identicon.git",
"reference": "65a50a5a8bd86b3591795937f9652b2e9075626c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bitverseio/identicon/zipball/65a50a5a8bd86b3591795937f9652b2e9075626c",
"reference": "65a50a5a8bd86b3591795937f9652b2e9075626c",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^5.0"
},
"type": "library",
"autoload": {
"psr-0": {
"": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kuba Birecki",
"email": "kuba.birecki@bitverse.io"
}
],
"description": "A PHP library for generating identicons.",
"support": {
"issues": "https://github.com/bitverseio/identicon/issues",
"source": "https://github.com/bitverseio/identicon/tree/master"
},
"time": "2015-11-01T21:19:42+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "7.8.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
"reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.1",
"guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
"php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.36 || ^9.6.15",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Jeremy Lindblom",
"email": "jeremeamia@gmail.com",
"homepage": "https://github.com/jeremeamia"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle is a PHP HTTP client library",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"psr-18",
"psr-7",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.8.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
"type": "tidelift"
}
],
"time": "2023-12-03T20:35:24+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "bbff78d96034045e58e13dedd6ad91b5d1253223"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223",
"reference": "bbff78d96034045e58e13dedd6ad91b5d1253223",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.36 || ^9.6.15"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.0.2"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
"type": "tidelift"
}
],
"time": "2023-12-03T20:19:20+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.6.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221",
"reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0"
},
"provide": {
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.36 || ^9.6.15"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.6.2"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
"type": "tidelift"
}
],
"time": "2023-12-03T20:05:35+00:00"
},
{
"name": "mtdowling/jmespath.php",
"version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/jmespath/jmespath.php.git",
"reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b",
"reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"symfony/polyfill-mbstring": "^1.17"
},
"require-dev": {
"composer/xdebug-handler": "^3.0.3",
"phpunit/phpunit": "^8.5.33"
},
"bin": [
"bin/jp.php"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"files": [
"src/JmesPath.php"
],
"psr-4": {
"JmesPath\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Declaratively specify how to extract elements from a JSON document",
"keywords": [
"json",
"jsonpath"
],
"support": {
"issues": "https://github.com/jmespath/jmespath.php/issues",
"source": "https://github.com/jmespath/jmespath.php/tree/2.7.0"
},
"time": "2023-08-25T10:54:48+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"time": "2023-09-23T14:17:50+00:00"
},
{
"name": "psr/http-factory",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "e616d01114759c4c489f93b099585439f795fe35"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
"reference": "e616d01114759c4c489f93b099585439f795fe35",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory/tree/1.0.2"
},
"time": "2023-04-10T20:10:41+00:00"
},
{
"name": "psr/http-message",
"version": "2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
},
"time": "2023-04-04T09:54:51+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.5.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
"reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-01-02T09:53:40+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-07-28T09:04:16+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.6.0"
}

View File

@@ -1,6 +1,6 @@
# API
## upload.php
# upload.php
- URL https://pictshare.net/api/upload.php
- Method: POST file
@@ -9,7 +9,7 @@
If the upload was successful answer will look like this
```
```json
{
"status":"ok",
"hash":"y1b6hr.jpg",
@@ -19,7 +19,7 @@ If the upload was successful answer will look like this
If there is an error the server will answer with status:err and a reason
```
```json
{
"status":"err",
"reason":"Unsupported filetype"
@@ -33,18 +33,114 @@ If there is an error the server will answer with status:err and a reason
```curl -F "file=@test.jpg" https://pictshare.net/api/upload.php```
Answer from the server:
```{"status":"ok","hash":"y1b6hr.jpg","url":"https://pictshare.net/y1b6hr.jpg"}```
```json
{"status":"ok","hash":"y1b6hr.jpg","url":"https://pictshare.net/y1b6hr.jpg"}
```
2. Uploading a file called test.jpg via curl and requesting a custom hash
2. Uploading from the commandline using alias, requires `jq` package for json response decoding
```curl -F "file=@test.jpg" -F "hash=helloworld.jpg" https://pictshare.net/api/upload.php```
Put this in your `.bashrc` or `.zshrc`:
```
pict () {
curl -s -F "file=@${1:--}" https://pictshare.net/api/upload.php | jq -r '.url';
}
```
Usage:
```
$ cat path/to/image.jpg | pict
```
Repsonse:
```
https://pictshare.net/y1b6hr.jpg
```
# geturl.php
- URL https://pictshare.net/api/geturl.php
- Method: GET
- Var name: url
- Answer type: JSON
Upload content by providing a link to the content. If the link points to a website, the HTML of the page is uploaded as a text bin.
```json
{
"status":"ok",
"hash":"y1b6hr.jpg",
"url":"https://pictshare.net/y1b6hr.jpg",
"delete_code": "aqxqlv3kqokxd15xpkqp8zjljpqerveu",
"delete_url": "https://pictshare.net/delete_aqxqlv3kqokxd15xpkqp8zjljpqerveu/2mr2va.txt"
}
```
If there is an error the server will answer with status:err and a reason
```json
{
"status":"err",
"reason":"Unsupported filetype"
}
```
### Examples
1. Uploading the HTML of xkcd.com
```curl -s https://pictshare.net/api/geturl.php?url=https://xkcd.com```
Answer from the server:
```{"status":"ok","hash":"helloworld.jpg","url":"https://pictshare.net/helloworld.jpg"}```
```json
{
"status": "ok",
"hash": "2mr2va.txt",
"url": "https://pictshare.net/2mr2va.txt",
"filetype": "text",
"delete_code": "aqxqlv3kqokxd15xpkqp8zjljpqerveu",
"delete_url": "https://pictshare.net/delete_aqxqlv3kqokxd15xpkqp8zjljpqerveu/2mr2va.txt"
}
```
2. Uploading a Video from Imgur
```curl https://pictshare.net/api/geturl.php?url=https://i.imgur.com/qQstLQt.mp4```
Answer from the server:
```json
{
"status": "ok",
"hash": "u0ni1m.mp4",
"url": "https://pictshare.net/u0ni1m.mp4",
"filetype": "mp4",
"delete_code": "aqxqlv3kqokxd15xpkqp8zjljpqerveu",
"delete_url": "https://pictshare.net/delete_aqxqlv3kqokxd15xpkqp8zjljpqerveu/u0ni1m.mp4"
}
```
3. Uploading from the commandline using alias, requires `jq` package for json response decoding
Put this in your `.bashrc` or `.zshrc`:
```
pictget () {
curl -s "hhttps://pictshare.net/api/geturl.php?url=$1" | jq -r '.url';
}
```
Usage:
```
$ pictget https://i.imgur.com/qQstLQt.mp4
```
Repsonse:
```
https://pictshare.net/u0ni1m.mp4
```
---
## pasetebin.php
# pasetebin.php
- URL https://pictshare.net/api/pastebin.php
- Method: POST/GET text
- Post var name: api_paste_code
@@ -56,7 +152,55 @@ This API can be used to directly post text. Server responds with the URL to the
Creating a new text bin that ready "Hello World"
```url -F "api_paste_code=Hello World" https://pictshare.net/api/pastebin.php```
```curl -F "api_paste_code=Hello World" https://pictshare.net/api/pastebin.php```
Answer from the server:
```https://pictshare.net/vekjy4e5rr.txt```
```https://pictshare.net/vekjy4e5rr.txt```
# info.php
- URL https://pictshare.net/api/info.php
- Method: POST/GET text
- Query var name: hash
- Answer: JSON
This API will get information about any given hash.
## Example
```curl https://pictshare.net/api/info.php?hash=9k3rbw.mp4```
Answer from the server:
```json
{
"status": "ok",
"hash": "9k3rbw.mp4",
"size_bytes": 2513225,
"size_interpreted": "2.4 MB",
"type": "video/mp4",
"type_interpreted": "mp4"
}
```
# base64.php
- URL https://pictshare.net/api/base64.php
- Method: POST/GET
- Query var name: base64
- Answer: JSON
## Example
Upload local image "test.jpg" to pictshare
```(echo -n "base64="; echo -n "data:image/jpeg;base64,$(base64 -w 0 test.jpg)") | curl --data @- https://pictshare.net/api/base64.php```
```json
{
"status": "ok",
"hash": "lpl119.jpg",
"url": "https://dev.pictshare.net/lpl119.jpg",
"filetype": "jpeg",
"delete_code": "z0e1mdo8szxnauspxp2f080e4wd4ycf2",
"delete_url": "https://dev.pictshare.net/delete_z0e1mdo8szxnauspxp2f080e4wd4ycf2/lpl119.jpg"
}
```

View File

@@ -1,9 +1,14 @@
# Configuration
PictShare can be configured using a single file: `inc/config.inc.php`
In this file you can set the following options. For a simple working example config file check out [/inc/example.config.inc.php](/inc/example.config.inc.php)
# Config options
|Option | value type | What it does|
|--- | --- | ---|
| URL | string | Sets the URL that will be shown to users for each upload. Must be set and must have tailing slash. eg: http://pictshare.local/ |
| ALT_FOLDER | string | All uploaded files will be copied to this location. This location can be a mounted network share (eg NFS or FTP, etc). If a file is not found in the normal upload direcotry, ALT_FOLDER will be checked. [more info about scaling PictShare](/rtfm/SCALING.md) |
| LOG_UPLOADER | bool | If set to true, all IP addresses of uploaders will be stored in /data/uploads.csv |
| FFMPEG_BINARY | string | If you installed ffmpeg on your machine, you can set the binary path here. This allows devices like the Raspberry Pi to be used with PictShare although I wouldn't recommend it because of the sloooooow conversion speed |
| PNG_COMPRESSION | int | 0 (no compression) to 9 (best compression) Note that for PNGs the compression doesn't affect the quality of the image, just the en/decode speed and file size |
@@ -12,6 +17,80 @@
| MASTER_DELETE_CODE | string | If set, this code will be accepted to delete any image by adding "delete_yourmasterdeletecode" to any image |
| MASTER_DELETE_IP | IP addr | If set, allows deletion of image no matter what delete code you provided if request is coming from this single IP |
| UPLOAD_FORM_LOCATION | string | If set, will only show the upload form if this url is requested. eg if you set it to /secret/upload then you only see the form if you go to http://your.pictshare.server/secret/upload but bare in mind that the uploads [via API](/rtfm/API.md) will still work for anyone|
| ALLOWED_SUBNET | IPv4 or IPv6 CIDR | If set, will limit uploads to IPs that match this CIDR |
| ALWAYS_WEBP | bool | If set to `true`, JPGs will always be served as WebP, if the client supports it (if `image/webp` is in header `HTTP_ACCEPT`) |
| UPLOAD_CODE | string | If set, all uploads require this code via GET or POST variable "uploadcode" to succeed |
| REDIS_SERVER (NOT IMPLEMENTED) | IP | If you define a REDIS server IP here, it will enable you to use the FFMPEG Worker |
| UPLOAD_QUOTA (NOT IMPLEMENTED) | int | Size in MB. If set, will only allow uploads if combined size of uploads on Server is smaller than this value. Does not account for ALT_FOLDER data and resized versions of original uploads won't be added to calculation |
| UPLOAD_CODE (NOT IMPLEMENTED | string | If set, all uploads require this code via GET or POST variable "uploadcode" or upload will fail |
| MAX_RESIZED_IMAGES (NOT IMPLEMENTED | string | If set, limits count of resized images/videos per file on server |
| MAX_RESIZED_IMAGES (NOT IMPLEMENTED | string | If set, limits count of resized images/videos per file on server |
# Content controllers
PictShare is not limited to handling just images. Various content types including txt,mp4 and even url shortenings are supported.
By default all of these are enabled but if you only need one or more, you can whitelist them and all others won't be accessible.
|Option | value type | What it does|
|--- | --- | ---|
| CONTENTCONTROLLERS | CSV string | If set, will whitelist content controllers for your instance. Must be uppercase and can be comma separated. Example: Only Pictures: `IMAGE`, Pictures and Videos: `IMAGE,VIDEO` |
Available values for the `CONTENTCONTROLLERS` setting are:
- IMAGE
- TEXT
- VIDEO
- URL
# Storage controllers
PictShare has an extention system that allows handling of multiple storage solutions or backends. If a requested file is not found locally, PictShare will ask all configured storage controllers if they have it, then download and serve it to the user.
If you want data on your external storage to be **encrypted**, you can set the following config setting. En/decryption is done automatically on up/download.
|Option | value type | What it does|
|--- | --- | ---|
|ENCRYPTION_KEY | base64 string | The key used to encrypt/decrypt files stored in storage controllers. See [/rtfm/ENCRYPTION.md](/rtfm/ENCRYPTION.md) for setup guide |
### Alternative Folder
The ALT_FOLDER option will copy every uploaded file from PictShare to a local path of your choice. This can be used to allow two instances of PictShare to serve the same data. Eg. you can mount a NFS share on your server and configure the ALT_FOLDER variable to point to that folder. All images are then stored on the NFS as well as your PictShare server.
|Option | value type | What it does|
|--- | --- | ---|
| ALT_FOLDER | string | All uploaded files will be copied to this location. This location can be a mounted network share (eg NFS or FTP, etc). If a file is not found in the normal upload direcotry, ALT_FOLDER will be checked. [more info about scaling PictShare](/rtfm/SCALING.md) |
### S3 (compatible) storage
You can also store all uploaded files on S3 or S3 compatible storage like [Minio](https://min.io/). This can also be used to scale your PictShare instance and have multiple distributed servers to serve the same files.
|Option | value type | What it does|
|--- | --- | ---|
|S3_BUCKET | string | Name of your [S3 bucket](https://aws.amazon.com/s3/) |
|S3_ACCESS_KEY | string | Access key for your bucket|
|S3_SECRET_KEY | string | Secret key for your bucket |
|S3_ENDPOINT | URL | Server URL. If you're using S3 compatible software like [Minio](https://min.io/) you can enter the URL here |
|S3_REGION | string | Region of your bucket |
### FTP
Oldschool, insecure and not that fast. But if you use it in combination with [Encryption](/rtfm/ENCRYPTION.md) this could be OK I guess. I don't judge.
This probably requires the php-ftp package but on some platforms it's included in the php-common package.
|Option | value type | What it does|
|--- | --- | ---|
|FTP_SERVER | string | IP or hostname of your FTP Server |
|FTP_PORT | int | Port number of your FTP Server. Defaults to 21 |
|FTP_SSL | bool | If your FTP server supports SSL-FTP (note: not sFTP! not the same), set it to true |
|FTP_USER | string | FTP Username |
|FTP_PASS | string | FTP Password |
|FTP_BASEDIR | string | Base path where files will be stored. Must end with / eg `/web/pictshare/` |
|FTP_PASSIVEMODE | bool | Wether to use passive mode or not. If you have troubles with uploading, switch this setting maybe |
# FFMPEG Worker
For faster video en/transcoding there is a php script called `/tools/ffmpeg_worker.php` which is a CLI application looping every second, checking the REDIS Queue for encoding tasks.
This way the rendering queue is detached from php-fpm since PHP can't have threaded workloads and it will make sure a encoding task like resizing of a video won't block the rest of the site from functioning.
For the FFMPEG Worker to be enabled, the config option `REDIS_SERVER` must be set to the IP Address of a redis server.

View File

@@ -1,32 +1,81 @@
# Docker
The fastest way to deploy PictShare is via the [official Docker repo](https://hub.docker.com/r/hascheksolutions/pictshare/)
- [Source code & more examples](https://github.com/HaschekSolutions/PictShare-Docker)
The fastest way to deploy PictShare is via the [official Docker package](https://github.com/HaschekSolutions/pictshare/pkgs/container/pictshare)
```bash
docker run -d -p 80:80 -e "TITLE=My own PictShare" -e "URL=http://localhost/" hascheksolutions/pictshare
docker run -d -p 80:80 -e "TITLE=My own PictShare" -e "URL=http://localhost/" hascheksolutions/pictshare:2
```
[![Docker setup](http://www.pictshare.net/b65dea2117.gif)](https://www.pictshare.net/8a1dec0973.mp4)
### Docker Compose With Prebuild Image by hascheksolutions
## Usage
Run container by docker-compose:
- First, install docker compose:
[Docker official docs](https://docs.docker.com/compose/install/)
- Pull docker-compose file:
### Building it
```bash
wget https://raw.githubusercontent.com/HaschekSolutions/pictshare/master/docker-compose.yml
```
- Edit docker-compose file:
```bash
vi docker-compose.yml
```
- Run container by docker-compose:
```bash
docker-compose up
docker build -t pictshare -f docker/Dockerfile .
```
By using this compose file, you should know that:
- Will make a directory "volumes" in the same directory where compose file is.
- Change `AUTOUPDATE` to false from true by defalt.
- And...it is highly recommended to build your own image.
### Quick start
```bash
docker run -d -p 80:80 --name=pictshare hascheksolutions/pictshare:2
```
### Persistent data
```bash
mkdir /data/pictshareuploads
chown 1000 -R /data/pictshareuploads
docker run -d -v /data/pictshareuploads:/var/www/data -p 80:80 --name=pictshare hascheksolutions/pictshare:2
```
### Persistent data with increased max upload size
```bash
mkdir /data/pictshareuploads
chown 1000 -R /data/pictshareuploads
docker run -d -e "MAX_UPLOAD_SIZE=1024" -v /data/pictshareuploads:/var/www/data -p 80:80 --name=pictshare hascheksolutions/pictshare:2
```
### Development
Using these commands it will mount the current directory in the docker container so you can develop locally without building after each change.
```bash
docker build -t pictshare -f docker/Dockerfile .
docker run -it --rm --name pictshare-dev -p 8080:80 -v $(pwd):/var/www -v $(pwd)/data:/var/www/data -e "URL=http://localhost:8080/" -e "SKIP_FILEPERMISSIONS=true" pictshare
```
## ENV Variables
There are some ENV variables that only apply to the Docker image
- MAX_UPLOAD_SIZE (int | size in MB that will be used for nginx. default 50)
Every other variable can be referenced against the [default PictShare configuration file](https://github.com/HaschekSolutions/pictshare/blob/master/inc/example.config.inc.php).
- TITLE (string | Title of the page)
- URL (string | URL that will be linked to new uploads)
- PNG_COMPRESSION (int | 0-9 how much compression is used. note that this never affects quality. default: 6)
- JPEG_COMPRESSION (int | 0-100 how high should the quality be? More is better. default: 90)
- MASTER_DELETE_CODE (string | code if added to any url, will delete the image)
- MASTER_DELETE_IP (string | ip which can delete any image)
- ALLOWED_SUBNET (CIDR IP range (can be comma separated) | IP subnets which are allowed to upload files)
- ALLOW_BLOATING (true/false | can images be bloated to higher resolutions than the originals)
- UPLOAD_CODE (string | Code that has to be supplied via POST or GET, to upload an image)
- UPLOAD_FORM_LOCATION (string | absolute path where upload gui will be shown)
- LOW_PROFILE (string | won't display error messages on failed uploads)
- IMAGE_CHANGE_CODE (string | code if provided, needs to be added to image to apply filter/rotation/etc)
- LOG_UPLOADER (true/false | log IPs of uploaders)
- MAX_RESIZED_IMAGES (int | how many versions of a single image may exist? -1 for infinite)
- SHOW_ERRORS (true/false | show upload/size/server errors?)
- SKIP_FILEPERMISSIONS (true/false | enables/disables fixing file permissions on start. default is false)
- ALWAYS_WEBP (true/false | Always tries to server JPGs as WEBp if the client supports it. Default is false)
- ALT_FOLDER (path to a folder where all hashes will be copied to and looked for offsite backup via nfs for example)
- S3_BUCKET (string | Name of your S3 bucket)
- S3_ACCESS_KEY (string | Access Key for your Bucket)
- S3_SECRET_KEY (string | Secrety Key)
- S3_REGION (string | S3 bucket region)
- S3_ENDPOINT (url | If you are using a selfhosted version of S3 like Minio, put your URL here)
- ENCRYPTION_KEY (string | If you want to use encryption for storage controllers, put your encryption key here. [Read more](https://github.com/HaschekSolutions/pictshare/blob/master/rtfm/ENCRYPTION.md))
- FTP_SERVER (string | IP or hostname of your FTP Server )
- FTP_PORT (int | Port of your FTP server (defaults to 21) )
- FTP_SSL (true/false | If FTP server supports SSL-FTP (not sFTP, thats not the same!))
- FTP_USER (string | FTP Username)
- FTP_PASS (string | FTP Password)
- FTP_BASEDIR (string | Base path where files will be stored. Must end with / eg `/web/pictshare/`)
- CONTENTCONTROLLERS (CSV string | If set, will whitelist content controllers for your instance. Must be uppercase and can be comma separated. Example: Only Pictures: `IMAGE`, Pictures and Videos: `IMAGE,VIDEO`)

37
rtfm/ENCRYPTION.md Normal file
View File

@@ -0,0 +1,37 @@
# Encryption
As of Jan. 2020 you can set up an encryption key in your config which will encrypt all images stored on [external storage](/rtfm/CONFIG.md#storage-controllers)
The files on the PictShare server are not encrypted, only the ones on external storage providers, eg if you want to use S3 as storage.
## Dependencies
To be able to use encryption you'll need the following extensions:
- mb-string (`apt-get install php-mbstring`)
- libsodium (`apt-get install php-libsodium`)
Since only files on [storage controllers](/rtfm/CONFIG.md#storage-controllers) are encrypted, you'll need to configure at least one.
## Preparation
First you'll need to generate a key and encode it in base64.
The easiest way to get it would be to run this command:
`php -r "echo base64_encode(sodium_crypto_secretstream_xchacha20poly1305_keygen());"`
This will output something like `SSdoJvp10ZvOY5v+vAcprxQKjNX1AzD52cAnFwr6yXc=`
Now put this output in your /inc/config.inc.php like this:
`define('ENCRYPTION_KEY','SSdoJvp10ZvOY5v+vAcprxQKjNX1AzD52cAnFwr6yXc=');`
**Warning: If you change or lose the ENCRYPTION_KEY, all encrypted data will be unrecoverably lost**
# How it works
If you have everything running you can upload a new image and it will get encrypted and uploaded to your storage container(s). This means you could even host on untrusted servers/buckets since nobody without the key will be able to decrypt it.
If you have uploaded a few files and see them on your storage container (eg S3) you'll notice the file has the '.enc' extension.
When you now wipe your PictShare instances local data folder and request the file again via the URL, the storage controller will pull the encrypted file, decrypt it and save it locally (unencrypted)

39
rtfm/IMAGEFILTERS.md Normal file
View File

@@ -0,0 +1,39 @@
# Filters for images
If the value field is defined you can use it like this: ```filtername_value```. eg: https://pictshare.net/pixelate_10/4qylps.jpg
Click on the images to see them in full size
| Filter | Value | Example image |
| --- | --- | --- |
| pixelate | 1-100 | [![](https://pictshare.net/pixelate/150/4qylps.jpg)](https://pictshare.net/pixelate/4qylps.jpg) |
| blur | 1-6 | [![](https://pictshare.net/blur/150/4qylps.jpg)](https://pictshare.net/blur/4qylps.jpg) |
| sepia | | [![](https://pictshare.net/sepia/150/4qylps.jpg)](https://pictshare.net/sepia/4qylps.jpg) |
| sepia2 | | [![](https://pictshare.net/sepia2/150/4qylps.jpg)](https://pictshare.net/sepia2/4qylps.jpg) |
| sharpen | | [![](https://pictshare.net/sharpen/150/4qylps.jpg)](https://pictshare.net/sharpen/4qylps.jpg) |
| emboss | | [![](https://pictshare.net/emboss/150/4qylps.jpg)](https://pictshare.net/emboss/4qylps.jpg) |
| cool | | [![](https://pictshare.net/cool/150/4qylps.jpg)](https://pictshare.net/cool/4qylps.jpg) |
| light | | [![](https://pictshare.net/light/150/4qylps.jpg)](https://pictshare.net/light/4qylps.jpg) |
| aqua | | [![](https://pictshare.net/aqua/150/4qylps.jpg)](https://pictshare.net/aqua/4qylps.jpg) |
| fuzzy | | [![](https://pictshare.net/fuzzy/150/4qylps.jpg)](https://pictshare.net/fuzzy/4qylps.jpg) |
| boost | | [![](https://pictshare.net/boost/150/4qylps.jpg)](https://pictshare.net/boost/4qylps.jpg) |
| boost2 | | [![](https://pictshare.net/boost2/150/4qylps.jpg)](https://pictshare.net/boost2/4qylps.jpg) |
| gray | | [![](https://pictshare.net/gray/150/4qylps.jpg)](https://pictshare.net/gray/4qylps.jpg) |
| antique | | [![](https://pictshare.net/antique/150/4qylps.jpg)](https://pictshare.net/antique/4qylps.jpg) |
| blackwhite | | [![](https://pictshare.net/blackwhite/150/4qylps.jpg)](https://pictshare.net/blackwhite/4qylps.jpg) |
| vintage | | [![](https://pictshare.net/vintage/150/4qylps.jpg)](https://pictshare.net/vintage/4qylps.jpg) |
| concentrate| | [![](https://pictshare.net/concentrate/150/4qylps.jpg)](https://pictshare.net/concentrate/4qylps.jpg) |
| hermajesty | | [![](https://pictshare.net/hermajesty/150/4qylps.jpg)](https://pictshare.net/hermajesty/4qylps.jpg) |
| everglow | | [![](https://pictshare.net/everglow/150/4qylps.jpg)](https://pictshare.net/everglow/4qylps.jpg) |
| freshblue | | [![](https://pictshare.net/freshblue/150/4qylps.jpg)](https://pictshare.net/freshblue/4qylps.jpg) |
| tender | | [![](https://pictshare.net/tender/150/4qylps.jpg)](https://pictshare.net/tender/4qylps.jpg) |
| dream | | [![](https://pictshare.net/dream/150/4qylps.jpg)](https://pictshare.net/dream/4qylps.jpg) |
| frozen | | [![](https://pictshare.net/frozen/150/4qylps.jpg)](https://pictshare.net/frozen/4qylps.jpg) |
| forest | | [![](https://pictshare.net/forest/150/4qylps.jpg)](https://pictshare.net/forest/4qylps.jpg) |
| rain | | [![](https://pictshare.net/rain/150/4qylps.jpg)](https://pictshare.net/rain/4qylps.jpg) |
| orangepeel | | [![](https://pictshare.net/orangepeel/150/4qylps.jpg)](https://pictshare.net/orangepeel/4qylps.jpg) |
| darken | | [![](https://pictshare.net/darken/150/4qylps.jpg)](https://pictshare.net/darken/4qylps.jpg) |
| summer | | [![](https://pictshare.net/summer/150/4qylps.jpg)](https://pictshare.net/summer/4qylps.jpg) |
| retro | | [![](https://pictshare.net/retro/150/4qylps.jpg)](https://pictshare.net/retro/4qylps.jpg) |
| country | | [![](https://pictshare.net/country/150/4qylps.jpg)](https://pictshare.net/country/4qylps.jpg) |
| washed | | [![](https://pictshare.net/washed/150/4qylps.jpg)](https://pictshare.net/washed/4qylps.jpg) |

View File

@@ -2,24 +2,29 @@
PictShare is written to be run on a linux server with PHP 7 and nginx. We tried to support Windows for some time but ever since we started integrating ffmpeg for MP4 hosting we ditched Windows.
- Make sure you have PHP7 GD libraries installed: ```apt-get install php-gd```
- Unpack the [PictShare zip](https://github.com/chrisiaut/pictshare/archive/master.zip)
It's highly recommended t hat you use [the official Docker container](https://github.com/HaschekSolutions/pictshare/pkgs/container/pictshare) so you don't have to do any manual setup. But if you know what you're doing, you can set it up yourself.
- Make sure you have all PHP7 libraries installed (note on some systems the packages are not called php7-* but just php-* also on some systems php7-mbstring is called php7-mb): ```apt-get install php7-exif php7-gd php7-json php7-openssl php7-fileinfo php7-mbstring php7-mcrypt```
- Run `composer install` from the `/lib` directory
- If you are not using windows, make sure your os have the ```file``` command working: ```apt-get install file```
- Unpack the [PictShare zip](https://github.com/hascheksolutions/pictshare/archive/master.zip)
- Rename /inc/example.config.inc.php to /inc/config.inc.php
- ```chmod +x bin/ffmpeg``` if you want to be able to use mp4 uploads
- The provided ffmpeg binary (bin/ffmpeg) is from [here](http://johnvansickle.com/ffmpeg/) and it's a 64bit linux executable. If you need a different one, load yours and overwrite the one provided or if you have ffmpeg installed on the server you can use the config var ```FFMPEG_BINARY``` to tell PictShare where to look for the binary
- If you want to be able to use mp4 uploads you need to supply your own FFMPEG binary or use the installed one on your distro useing the config var ```FFMPEG_BINARY```. For example it should point to `/usr/bin/ffmpeg` if you installed ffmpeg through your package manager
- Since default upload sizes will be 2M in PHP you should edit your php.ini and change ```upload_max_filesize``` and ```post_max_size``` to a larger value
- (optional) You can and should put a [nginx](https://www.nginx.com/) proxy before the Apache server. That thing is just insanely fast with static content like images.
- (optional) You can and should use [nginx](https://www.nginx.com/) as your web server. Check [/docker/rootfs/nginx.conf] for an example on how the nginx config should look like
- (optional) To secure your traffic I'd highly recommend getting an [SSL Cert](https://letsencrypt.org/) for your server if you don't already have one.
## Upgrading
- Just re-download the [PictShare zip](https://github.com/chrisiaut/pictshare/archive/master.zip) file and extract and overwrite existing pictshare files. Uploads and config won't be affected.
- Check if your ```/inc/config.inc.php``` file has all settings required by the ```/inc/example.config.inc.php``` since new options might get added in new versions
- On docker just `docker pull hascheksolutions/pictshare:2` and run the newer image
- Manual upgrade:
- Just re-download the [PictShare zip](https://github.com/hascheksolutions/pictshare/archive/master.zip) file and extract and overwrite existing pictshare files. Uploads and config won't be affected.
- Check if your ```/inc/config.inc.php``` file has all settings required by the ```/inc/example.config.inc.php``` since new options might get added in new versions
```bash
# to be run from the directory where your pictshare directory sits in
git clone https://github.com/chrisiaut/pictshare.git temp
git clone https://github.com/hascheksolutions/pictshare.git temp
cp -r temp/* pictshare/.
rm -rf temp
```
@@ -59,4 +64,4 @@ server {
}
}
```
```

View File

@@ -2,5 +2,141 @@
- [Pastebinit](/rtfm/PASTEBINIT.md)
- Chrome Browser extension: https://chrome.google.com/webstore/detail/pictshare-1-click-imagesc/mgomffcdpnohakmlhhjmiemlolonpafc
- Source: https://github.com/chrisiaut/PictShare-Chrome-extension
- Plugin to upload images with ShareX: https://github.com/ShareX/CustomUploaders/blob/master/pictshare.net.sxcu
- Source: https://github.com/hascheksolutions/PictShare-Chrome-extension
- Plugin to upload images with ShareX: https://github.com/ShareX/CustomUploaders/blob/master/pictshare.net.sxcu
# Upload from CLI
Requirements:
- curl (apt-get install curl)
- jq (apt-get install jq)
```bash
#!/bin/bash
# filename: pictshare.sh
# usage: ./pictshare.sh /path/to/image.jpg
result=$(curl -s -F "file=@${1}" https://pictshare.net/api/upload.php | jq -r .url)
echo $result
```
# Screenshot to pictshare (linux)
This script will create a screenshot (you can choose the area), uploads it to PictShare, copies the raw image to your clipborad and opens the image on PictShare in Chrome
Requirements:
- curl (apt-get install curl)
- jq (apt-get install jq)
- screenshooter (apt-get install xfce4-screenshooter)
```bash
#!/bin/bash
# filename: screenshot2pictshare.sh
# usage: ./screenshot2pictshare.sh
if [[ $# -eq 0 ]] ; then
xfce4-screenshooter -r -o $0
exit 0
fi
result=$(curl -s -F "file=@${1}" https://pictshare.net/api/upload.php | jq -r .url)
xclip -selection clipboard -t image/png -i $1
google-chrome $result
```
# Screenshot to pictshare (windows)
This script will upload a screenshot from [Greenshot](https://getgreenshot.org/) to PictShare with the help of a PowerShell script.
Requirements:
- curl (choco install curl)
- [PowerShell 7](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows)
Configure Greenshot:
- Settings -> Output
- Storage location: C:\Temp\
- Filename pattern: GreenShot
- Image format: jpg
Create a new External command, under Settings -> Plugins -> Click on External command Plugin -> Configure -> New
- Name: what ever you want here
- Command: find and point to pwsh.exe
- Argument: -w Hidden -F Path\to\this\script -Address consto.com
Create a PowerShell script with the code below.
Feel free to change "C:\Temp\GreenShot.jpg" and "C:\Temp\pictshare_posts.json" to match your needs.
pictshare_posts.json is useful for logging and automating deleting old uploads.
```powershell
#Requires -Version 7
param (
# Change the base url to match your Pictshare server
[Parameter(Mandatory)][string]$Address,
# Change path of where you expect the jpg file to be
[string]$File = "C:\Temp\GreenShot.jpg",
# Log file of all requests
[string]$LogFile = "C:\Temp\pictshare_posts.json",
# Use http and not https
[switch]$IsNotHttps,
# Do not save url to upload to the clipboard
[switch]$NoSaveToClipboard
)
begin {
$Protocol = if ($IsNotHttps){
"http:"
}else{
"https:"
}
}
process {
# Upload screenshot
$Response = $(curl -s -F "file=@$File" $Protocol//$Address/api/upload.php) | ConvertFrom-Json
if ($Response.status -like "ok") {
if ($NoSaveToClipboard){
# Don't save url to clipboard
}else{
Set-Clipboard -Value $Response.url
}
}
# Output response back from the pictshare server
if($Response){
$Response | ConvertTo-Json | Out-File -FilePath $LogFile -Append
}
}
end {}
# PHP
```php
/*
* @param $path string Path to the file that should be uploaded
* @param $hash string Optional. File name we want on pictshare for the file
*/
function pictshareUploadImage($path,$hash=false)
{
if(!file_exists($path)) return false;
$request = curl_init('https://pictshare.net/api/upload.php');
curl_setopt($request, CURLOPT_POST, true);
curl_setopt(
$request,
CURLOPT_POSTFIELDS,
array(
'file' => curl_file_create($path),
'hash'=>$hash
));
// output the response
curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
$json = json_decode(curl_exec($request).PHP_EOL,true);
// close the session
curl_close($request);
return $json;
}
```

34
rtfm/MODIFIERS.md Normal file
View File

@@ -0,0 +1,34 @@
## Images
### Resize
```
/800x600/d8c01b45a6.png
```
width x height
### Rotate
```
/upside|left|right/d8c01b45a6.png
```
* `upside`: 180°
* `left`: 90°
* `right`: -90°
### WebP conversion
```
/webp/d8c01b45a6.jpeg
```
### Gif to mp4
```
/mp4/d8c01b45a6.gif
```
### Filters
```
/filter/d8c01b45a6.png
```
[See available filters](IMAGEFILTERS.md)

View File

@@ -1,6 +1,6 @@
# How to scale PictShare
If your library is huge then you might want to think about scaling your instances. Pictshare (v2+) was rebuilt with scaling in mind but instead of built-in scaling features we use a smarter system
If your library is huge then you might want to think about scaling your instances. Pictshare (v2+) was rebuilt with scaling in mind but instead of built-in scaling features we rely on OS level solutions.
# The "ALT_FOLDER" setting
You can set the config var ```ALT_FOLDER``` to point to a directory on the same server where pictshare will look for content and put new uploads.
@@ -9,4 +9,46 @@ This allows you to have a shared or even a mounted ftp/nfs folder that will act
The main site https://pictshare.net uses this technique to scale across many servers in multiple countries.
Using this method you can have multiple servers for the same domain (with a reverse proxy)
Using this method you can have multiple servers for the same domain (with a reverse proxy)
# Fast, read only instances
PictShare needs strong hardware for video conversion but using smart Nginx configurations you can host an instance on a weak but fast server and relay all uploads to another server with stronger hardware running PictShare
**Example Nginx config for a small VPS that relay all uploads to a faster but private server**
```
server {
listen 80;
server_name your.awesome.domain.name;
client_max_body_size 50M; # Set the max file upload size. This needs to be equal or larger than the size you specified in your php.ini
root /var/www/pictshare; # or where ever you put it
index index.php;
location / {
try_files $uri $uri/ /index.php?url=$request_uri;
}
# Magic begins here. Since all uploads have to be made via some script in the /api/ folder we can just redirect
# these requests to another server that doesn't need to be public facing
location ^~ /api/ {
proxy_pass http://10.12.0.3/; #set to the hostname or ip or url of the powerful server
include /etc/nginx/proxy_params;
}
location ~ \.php {
fastcgi_pass unix:/var/run/php/php7.3-fpm.sock; #may be slightly different depending on your php version
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;
}
location ~ /(data|tmp|bin|content-controllers|inc|interfaces|storage-controllers|templates|tools) {
deny all;
return 404;
}
}
```

View File

@@ -13,23 +13,39 @@ class AltfolderStorage implements StorageController
return file_exists($altname);
}
function pullFile($hash)
function getItems($dev=false)
{
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(ALT_FOLDER.DS));
$files = array();
foreach ($rii as $file) {
if ($file->isDir())
continue;
$files[] = $file->getFilename();
}
return $files;
}
function pullFile($hash,$location)
{
$altname=ALT_FOLDER.DS.$hash;
if(file_exists($altname))
{
storeFile($altname,$hash,false);
copy($altname,$location);
}
}
function pushFile($hash)
function pushFile($source,$hash)
{
$altname=ALT_FOLDER.DS.$hash;
$orig = ROOT.DS.'data'.DS.$hash.DS.$hash;
if(file_exists($orig) && !$this->hashExists($hash))
if(!$this->hashExists($hash))
{
copy($orig,$altname);
}
copy($source,$altname);
return true;
}
return false;
}
function deleteFile($hash)

View File

@@ -0,0 +1,145 @@
<?php
class FTPStorage implements StorageController
{
private $connection;
private $login;
function __destruct()
{
if($this->connection)
ftp_close($this->connection);
}
function connect()
{
if(!$this->connection)
{
if(defined('FTP_SSL') && FTP_SSL === true)
$this->connection = ftp_ssl_connect(FTP_SERVER, ((defined('FTP_SSL') && is_numeric(FTP_PORT))?FTP_PORT:21) );
else
$this->connection = ftp_connect(FTP_SERVER, ((defined('FTP_SSL') && is_numeric(FTP_PORT))?FTP_PORT:21) );
}
if($this->connection && !$this->login)
{
$this->login = ftp_login($this->connection, FTP_USER, FTP_PASS);
if( (defined('FTP_PASSIVEMODE') && FTP_PASSIVEMODE === true) || !defined('FTP_PASSIVEMODE') )
{
ftp_set_option($this->connection, FTP_USEPASVADDRESS, false);
ftp_pasv($this->connection, TRUE);
}
else
ftp_pasv($this->connection, false);
}
// Was the connection successful?
if ((!$this->connection) || (!$this->login)) {
$this->connection = false;
return false;
}
return true;
}
function isEnabled()
{
return (defined('FTP_SERVER') && FTP_SERVER &&
defined('FTP_USER') && FTP_USER &&
defined('FTP_PASS') && FTP_PASS);
}
function hashExists($hash)
{
if(!$this->connect()) return null;
$subdir = $this->hashToDir($hash);
$ftpfilepath = FTP_BASEDIR.$subdir.'/'.$hash;
if(@ftp_chdir($this->connection, FTP_BASEDIR.$subdir))
return (ftp_size($this->connection,$ftpfilepath)>0?true:false);
return false;
}
function getItems($dev=false)
{
if(!$this->connect()) return false;
return $this->ftp_list_files_recursive(FTP_BASEDIR,$dev);
}
function pullFile($hash,$location)
{
if(!$this->connect()) return false;
$subdir = $this->hashToDir($hash);
$ftpfilepath = FTP_BASEDIR.$subdir.'/'.$hash;
return ftp_get($this->connection, $location, $ftpfilepath, FTP_BINARY);
}
function pushFile($source,$hash)
{
if(!$this->connect()) return false;
$subdir = $this->hashToDir($hash);
$ftpfilepath = FTP_BASEDIR.$subdir.'/'.$hash;
$this->ftp_mksubdirs($subdir);
return ftp_put($this->connection, $ftpfilepath, $source, FTP_BINARY);
}
function deleteFile($hash)
{
if(!$this->connect()) return false;
$subdir = $this->hashToDir($hash);
$ftpfilepath = FTP_BASEDIR.$subdir.'/'.$hash;
return (ftp_delete($this->connection,$ftpfilepath)?true:false);
}
function hashToDir($hash)
{
$md5 = md5($hash);
$dir = $md5[0].'/'.$md5[1].'/'.$md5[2];
return $dir;
}
function ftp_mksubdirs($ftpath)
{
if(!$this->connect()) return false;
@ftp_chdir($this->connection, FTP_BASEDIR);
$parts = array_filter(explode('/',$ftpath), function($value) {
return ($value !== null && $value !== false && $value !== '');
});
foreach($parts as $part){
$part = strval($part);
if(!@ftp_chdir($this->connection, $part)){
ftp_mkdir($this->connection, $part);
ftp_chdir($this->connection, $part);
}
}
}
function ftp_list_files_recursive($path,$dev=false)
{
if(!$this->connect()) return false;
$items = ftp_mlsd($this->connection, $path);
$result = array();
if(is_array($items))
foreach ($items as $item)
{
$name = $item['name'];
$type = $item['type'];
$filepath = $path.'/'. $name;
if ($type == 'dir')
{
$result =
array_merge($result, $this->ftp_list_files_recursive($filepath,$dev));
}
else if(mightBeAHash($name) || endswith($name,'.enc'))
{
$result[] = $name;
if($dev===true) echo " Got $name \r";
}
}
return $result;
}
}

View File

@@ -0,0 +1,108 @@
<?php
/**
* Config needed
*
* S3_BUCKET
* S3_ACCESS_KEY
* S3_SECRET_KEY
* (optional) S3_ENDPOINT
*/
class S3Storage implements StorageController
{
private $s3;
function connect(){
$this->s3 = new Aws\S3\S3Client([
'version' => 'latest',
'region' => (defined('S3_REGION') && S3_REGION ?S3_REGION:'us-east-1'),
'endpoint' => S3_ENDPOINT,
'use_path_style_endpoint' => true,
'credentials' => [
'key' => S3_ACCESS_KEY,
'secret' => S3_SECRET_KEY,
],
]);
}
function isEnabled()
{
return (defined('S3_BUCKET') && S3_BUCKET);
}
function hashExists($hash)
{
if(!$this->s3)$this->connect();
return $this->s3->doesObjectExist(S3_BUCKET,$hash);
}
function getItems($dev=false)
{
if(!$this->s3)$this->connect();
$KeyCount = 9999;
$keys = 100; //the amount of keys we'll receive per request. 1000 max but that times out sometimes
$lastkey = false;
$count = 0;
$items = array();
while($KeyCount>=$keys)
{
$objects = $this->s3->listObjectsV2([
'Bucket' => S3_BUCKET,
'MaxKeys'=> $keys,
'StartAfter'=>($lastkey?$lastkey:'')
]);
++$count;
foreach ($objects['Contents'] as $object){
$lastkey = $object['Key'];
$items[] = $lastkey;
}
if($dev===true) echo " Got ".($count*$keys)." files \r";
$KeyCount = $objects['KeyCount'];
}
return $items;
}
function pullFile($hash,$location)
{
if(!$this->s3)$this->connect();
if(!$this->hashExists($hash)) return false;
$this->s3->getObject([
'Bucket' => S3_BUCKET,
'Key' => $hash,
'SaveAs' => $location
]);
return true;
}
function pushFile($source,$hash)
{
if(!$this->s3)$this->connect();
$this->s3->putObject([
'Bucket' => S3_BUCKET,
'Key' => $hash,
'SourceFile' => $source
]);
return true;
}
function deleteFile($hash)
{
if(!$this->s3)$this->connect();
$this->s3->deleteObject([
'Bucket' => S3_BUCKET,
'Key' => $hash
]);
}
}

View File

@@ -14,6 +14,10 @@
<link href="/css/pictshare.css" rel="stylesheet">
<link href="/css/dropzone.css" rel="stylesheet">
<!-- github-fork-ribbon-css
https://simonwhitaker.github.io/github-fork-ribbon-css/ -->
<link href="/css/gh-fork-ribbon.min.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
@@ -21,6 +25,10 @@
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script>
var maxUploadFileSize=<?php echo (int)(ini_get('upload_max_filesize'));?>
</script>
<meta name="description" content="Free image sharing, linking and tracking">
<meta name="keywords" content="image, share, hosting, free">
<meta name="robots" content="index, follow">
@@ -31,18 +39,33 @@
<meta name="rating" content="general">
</HEAD>
<BODY><a href="https://github.com/HaschekSolutions/pictshare"><img style="position: absolute; top: 0; left: 0; border: 0;" src="https://camo.githubusercontent.com/82b228a3648bf44fc1163ef44c62fcc60081495e/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f6c6566745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_left_red_aa0000.png"></a>
<BODY>
<a class="github-fork-ribbon left-top" href="https://github.com/HaschekSolutions/pictshare" data-ribbon="Fork me on GitHub" title="Fork me on GitHub">Fork me on GitHub</a>
<div class="container" id="headcontainer">
<div class="row">
<div class="col-md-8">
<a href="/"><img src="/css/imgs/logo/horizontal3.png" /></a>
<?php
if(file_exists(ROOT.DS.'notice.txt'))
echo '<div class="alert alert-warning" role="alert">'.file_get_contents(ROOT.DS.'notice.txt').'</div>';
?>
<div class="well">
<?php if($forbidden===true) { ?>
<h2>Upload forbidden</h2>
<p>Due to configured restrictions, you are not allowed to upload files at this time</p>
<?php } else { ?>
<div id="uploadinfo"></div>
<p>
<?php
echo "Max Upload size: ". (int)(ini_get('upload_max_filesize'))."MB / File<br/>";
echo "Allowed file types: ". implode(', ',getAllContentFiletypes());
if(defined('UPLOAD_CODE') && UPLOAD_CODE!='')
echo '<br>Upload Code: <input type="password" id="uploadcode" />';
?>
</p>
<form class="dropzone well" id="dropzone" method="post" action="/api/upload.php" enctype="multipart/form-data">
@@ -50,6 +73,7 @@
<input name="file" type="file" multiple />
</div>
</form>
<?php } ?>
</div>
</div>
</div>
@@ -63,4 +87,4 @@
<script src="/js/dropzone.js"></script>
<script src="/js/pictshare.js"></script>
</BODY>
</HTML>
</HTML>

View File

@@ -41,9 +41,9 @@
<video id="video" poster="<?php echo URL.$url.'/preview/'.$hash; ?>" preload="auto" autoplay="autoplay" controls muted="muted" loop="loop" webkit-playsinline>
<source src="<?php echo URL.$url.'/raw' ?>" type="video/mp4">
<?php
if(file_exists(ROOT.DS.'data'.DS.$hash.DS.'webm_1.'.$hash))
if(file_exists(getDataDir().DS.$hash.DS.'webm_1.'.$hash))
echo '<source src="'.URL.'raw/webm/'.$hash.'" type="video/webm">'."\n";
if(file_exists(ROOT.DS.'data'.DS.$hash.DS.'ogg_1.'.$hash))
if(file_exists(getDataDir().DS.$hash.DS.'ogg_1.'.$hash))
echo '<source src="'.URL.'raw/ogg/'.$hash.'" type="video/ogg">'."\n";
?>
</video>

0
tmp/.gitignore vendored Normal file → Executable file
View File

View File

@@ -1,99 +0,0 @@
<?php
/*
* Alternative folder upload
* This tool copies all raw images/videos/gifs to the defined ALT_FOLDER location
* This will create a copy in the location. The location can be a mounted external server like CIFS or sshfs
* This will allow you to store a backup of your images on some other server
*
*/
if(php_sapi_name() !== 'cli') exit('This script can only be called via CLI');
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).DS.'..');
include_once(ROOT.DS.'inc/config.inc.php');
include_once(ROOT.DS.'inc/core.php');
if(!defined('ALT_FOLDER') || !ALT_FOLDER)
die("[X] Error: You should define the ALT_FOLDER config in your inc/config.inc.php first");
$pm = new PictshareModel();
if(in_array('sim',$argv))
{
echo "[!!!!] SIMULATION MODE. Nothing will be uploaded [!!!!] \n\n";
$sim = true;
}
else $sim = false;
//gather local data
echo "[i] Looping through local files\n";
$dir = ROOT.DS.'data'.DS;
$dh = opendir($dir);
$localfiles = array();
$allhashes=0;$allsize=0;
$skips=0;$skipsize=0;
$copied=0;$copysize=0;
$errors=0;$errorsize=0;
while (false !== ($hash = readdir($dh))) {
if($hash=='.'||$hash=='..') continue;
$img = $dir.$hash.DS.$hash;
if(!file_exists($img)) continue;
$info = strtolower(pathinfo($img, PATHINFO_EXTENSION));
$thissize = filesize($img);
$type = $pm->isTypeAllowed($info);
++$allhashes;
$allsize+=$thissize;
if($type)
{
if(file_exists(ALT_FOLDER.DS.$hash))
{
echo " [!] Skipping existing $hash \n";
++$skips;
$skipsize+=$thissize;
}
else
{
++$copied;
$copysize+=$thissize;
echo "[i] Copying $hash to ".ALT_FOLDER.DS.$hash." \r";
if($sim===false)
copy($img,ALT_FOLDER.DS.$hash);
}
}
else
{
++$errors;
$errorsize+=$thissize;
echo " [X] ERROR $hash not allowed format: $info \n";
}
}
echo "\n[i] Done\n";
echo "\n----------- STATS ----------\n\n";
echo " All files found:\t$allhashes\t".renderSize($allsize)."\n";
echo " Copied files:\t$copied\t".renderSize($copysize)."\n";
echo " Skipped files:\t$skips\t".renderSize($skipsize)."\n";
echo " Erroneous files:\t$errors\t".renderSize($errorsize)."\n";
echo "\n";
function renderSize($bytes, $precision = 2) {
$units = array('B', 'KB', 'MB', 'GB', 'TB');
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
// Uncomment one of the following alternatives
$bytes /= pow(1024, $pow);
// $bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow];
}

View File

@@ -1,91 +0,0 @@
<?php
/*
* Backblaze uploader
* This tool uploads all local images to backblaze if they don't exist yet
* You can use this to backup your images to backblaze or set it up as your data source for scaling
*
*/
if(php_sapi_name() !== 'cli') exit('This script can only be called via CLI');
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).DS.'..');
include_once(ROOT.DS.'inc/config.inc.php');
include_once(ROOT.DS.'inc/core.php');
include_once(ROOT.DS.'classes/backblaze.php');
$pm = new PictshareModel();
if(in_array('sim',$argv))
{
echo "[!!!!] SIMULATION MODE. Nothing will be uploaded [!!!!] \n\n";
$sim = true;
}
else $sim = false;
if(in_array('recentlyrendered',$argv))
{
echo "[O] Will only upload if the image was recently viewed. Recently meaning now minus one year \n\n";
$recentonly = true;
}
else $recentonly = false;
$b = new Backblaze();
echo "[i] Loading file list from Backblaze ..";
$remotefiles = $b->getAllFilesInBucket();
echo " done. Got ".count($remotefiles)." files\n";
$uploadsize = 0;
//gather local data
$dir = ROOT.DS.'data'.DS;
$dh = opendir($dir);
$localfiles = array();
echo "[i] Loading local files ..";
while (false !== ($filename = readdir($dh))) {
$img = $dir.$filename.DS.$filename;
if(!file_exists($img)) continue;
$type = strtolower(pathinfo($img, PATHINFO_EXTENSION));
$type = $pm->isTypeAllowed($type);
if($type)
{
if($recentonly===true)
{
$recent = @file_get_contents($dir.$filename.DS.'last_rendered.txt');
if(!$recent || (time()-$recent) > 3600*24*365) continue;
}
$localfiles[] = $filename;
}
}
echo " done. Got ".count($localfiles)." files\n";
echo "[i] Checking if there are local files that are not remote\n";
foreach($localfiles as $hash)
{
if(!$remotefiles[$hash])
{
echo " [!] $hash not found on BB. Uploading...";
if($sim!==true)
$b->upload($hash);
$uploadsize+=filesize($dir.$hash.DS.$hash);
echo " done.\tUploaded so far: ".renderSize($uploadsize)."\n";
}
}
function renderSize($bytes, $precision = 2) {
$units = array('B', 'KB', 'MB', 'GB', 'TB');
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
// Uncomment one of the following alternatives
$bytes /= pow(1024, $pow);
// $bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow];
}

View File

@@ -1,97 +0,0 @@
<?php
/*
* Cleanup
* This script cleans up all uploads and only leaves the original files
* So if you have an image and it was converted to change sizes, these files are deleted
* And will be re-created next time they are requested
*
* usage: php cleanup.php [sim]
*
* Params:
* sim => Just simulate everything, don't actually delete
*/
if(php_sapi_name() !== 'cli') exit('This script can only be called via CLI');
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).DS.'..');
include_once(ROOT.DS.'inc/config.inc.php');
include_once(ROOT.DS.'inc/core.php');
$pm = new PictshareModel();
if(in_array('sim',$argv))
{
echo "[!!!!] SIMULATION MODE. Nothing will be deleted [!!!!] \n\n";
$sim = true;
}
else $sim = false;
$dir = ROOT.DS.'data'.DS;
$dh = opendir($dir);
$localfiles = array();
if(in_array('noskip',$argv))
{
echo "Won't skip existing files\n\n";
$allowskipping = false;
}
else
$allowskipping = true;
//making sure ffmpeg is executable
system("chmod +x ".ROOT.DS.'bin'.DS.'ffmpeg');
echo "[i] Finding local mp4 files ..";
while (false !== ($filename = readdir($dh))) {
$img = $dir.$filename.DS.$filename;
if(!file_exists($img)) continue;
$type = strtolower(pathinfo($img, PATHINFO_EXTENSION));
$type = $pm->isTypeAllowed($type);
if($type)
$localfiles[] = $filename;
}
if(count($localfiles)==0) exit('No files found'."\n");
echo " done. Got ".count($localfiles)." folders\n";
$sumsize = 0;
echo "[i] Looking for files to clean up\n";
foreach($localfiles as $hash)
{
$dir = ROOT.DS.'data'.DS.$hash.DS;
$dh = opendir($dir);
while (false !== ($filename = readdir($dh))) {
if(!is_file($dir.$filename) || $filename==$hash || $filename == 'last_rendered.txt')
continue;
echo "[$hash] $filename";
$sumsize+=filesize($dir.$filename);
if(!$sim)
unlink($dir.$filename);
echo "\t".(file_exists($dir.$filename)?'NOT DELETED':'DELETED')."\n";
}
}
echo "\n[!] Finished! Deleted ".renderSize($sumsize)."\n";
function renderSize($bytes, $precision = 2) {
$units = array('B', 'KB', 'MB', 'GB', 'TB');
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
// Uncomment one of the following alternatives
$bytes /= pow(1024, $pow);
// $bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow];
}

46
tools/cron.php Normal file
View File

@@ -0,0 +1,46 @@
<?php
if(php_sapi_name() !== 'cli') exit('This script can only be called via CLI');
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
ini_set('memory_limit', -1);
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).DS.'..');
include_once(ROOT.DS.'inc/config.inc.php');
include_once(ROOT.DS.'inc/core.php');
switch($argv[1])
{
case 'uploadqueue':
uploadqueue();
break;
default:
exit("[ERR] Command not found. Available commands are: uploadqueue");
}
function uploadqueue()
{
$queuefile = ROOT.DS.'tmp'.DS.'controllerqueue.txt';
if(!file_exists($queuefile))
exit("[i] File does not exist (nothing to upload)\n");
$queue = file($queuefile);
if(count($queue)<1)
exit("[i] Nothing to upload\n");
$newqueue = array();
foreach($queue as $hash)
{
$hash = trim($hash);
echo " [i] Checking $hash\n";
$hash = trim($hash);
if(isExistingHash($hash)) //check if hash is still on server
{
echo " [$hash] still exists locally. Uploading.. ";
$success = storageControllerUpload($hash); // and retry the upload
echo ($success===true?' => SUCCESS. Removing from queue':'FAILED. Will be re-added to queue')."\n";
if(!$success)
$newqueue[]=$hash;
}
}
file_put_contents($queuefile,implode("\n",$newqueue));
}

2
tools/ffmpeg_worker.php Normal file
View File

@@ -0,0 +1,2 @@
<?php

View File

@@ -31,7 +31,7 @@ require_once(ROOT . DS . 'content-controllers' . DS. 'video'. DS . 'video.contro
if(!defined('FFMPEG_BINARY')||FFMPEG_BINARY=='' || !FFMPEG_BINARY) exit('Error: FFMPEG_BINARY not defined, no clue where to look');
$vc = new VideoController();
$dir = ROOT.DS.'data'.DS;
$dir = getDataDir().DS;
$dh = opendir($dir);
$localfiles = array();
@@ -57,7 +57,7 @@ if(in_array('altfolder',$argv) && defined('ALT_FOLDER') && ALT_FOLDER && is_dir(
echo "\n [i] $filename is ..\t";
$valid = $vc->rightEncodedMP4($vid);
$tmp = ROOT.DS.'tmp'.DS.$hash;
$cmd = FFMPEG_BINARY." -loglevel panic -y -i $vid -vcodec libx264 -an -profile:v baseline -level 3.0 -pix_fmt yuv420p -vf \"scale=trunc(iw/2)*2:trunc(ih/2)*2\" $tmp && cp $tmp $img";
$cmd = FFMPEG_BINARY." -loglevel panic -y -i $vid -vcodec libx264 -profile:v baseline -level 3.0 -pix_fmt yuv420p -vf \"scale=trunc(iw/2)*2:trunc(ih/2)*2\" $tmp && cp $tmp $img";
echo ($valid?'Valid'."\n":'Not valid => Converting..');
if(!$valid)
{
@@ -69,9 +69,6 @@ if(in_array('altfolder',$argv) && defined('ALT_FOLDER') && ALT_FOLDER && is_dir(
}
}
//making sure ffmpeg is executable
system("chmod +x ".ROOT.DS.'bin'.DS.'ffmpeg');
if(count($localfiles)==0)
{
echo "[i] Finding local mp4 files\n";
@@ -106,7 +103,7 @@ foreach($localfiles as $hash)
{
$mp4 = $dir.$hash.DS.$hash;
$tmp = ROOT.DS.'tmp'.DS.$hash;
$cmd = FFMPEG_BINARY." -loglevel panic -y -i $mp4 -vcodec libx264 -an -profile:v baseline -level 3.0 -pix_fmt yuv420p -vf \"scale=trunc(iw/2)*2:trunc(ih/2)*2\" $tmp && cp $tmp $mp4";
$cmd = FFMPEG_BINARY." -loglevel panic -y -i $mp4 -vcodec libx264 -profile:v baseline -level 3.0 -pix_fmt yuv420p -vf \"scale=trunc(iw/2)*2:trunc(ih/2)*2\" $tmp && cp $tmp $mp4";
echo " [i] Converting '$hash'";
system($cmd);
if(defined('ALT_FOLDER') && ALT_FOLDER && is_dir(ALT_FOLDER))

View File

@@ -4,7 +4,7 @@ define('ROOT', dirname(__FILE__).DS.'..');
echo "[i] Starting recreation of hashes.csv\n";
$dir = ROOT.DS.'data'.DS;
$dir = getDataDir().DS;
$dh = opendir($dir);
$fp = fopen($dir.'hashes.csv','w');

View File

@@ -22,7 +22,7 @@ include_once(ROOT.DS.'inc/core.php');
$pm = new PictshareModel();
$dir = ROOT.DS.'data'.DS;
$dir = getDataDir().DS;
$dh = opendir($dir);
$localfiles = array();
@@ -34,9 +34,6 @@ if(in_array('noskip',$argv))
else
$allowskipping = true;
//making sure ffmpeg is executable
system("chmod +x ".ROOT.DS.'bin'.DS.'ffmpeg');
echo "[i] Finding local mp4 files ..";
while (false !== ($filename = readdir($dh))) {
$img = $dir.$filename.DS.$filename;

View File

@@ -0,0 +1,168 @@
<?php
/*
* Storage controller sync
* This tool copies syncs local files to storage controllers
*
*/
if(php_sapi_name() !== 'cli') exit('This script can only be called via CLI');
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
ini_set('memory_limit', -1);
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__).DS.'..');
include_once(ROOT.DS.'inc/config.inc.php');
include_once(ROOT.DS.'inc/core.php');
$dir = getDataDir().DS;
$sc = getStorageControllers();
$count = 0;
$controllers = array();
$filehashes = [];
foreach($sc as $contr)
{
if((new $contr())->isEnabled()===true)
{
$controllers[] = new $contr();
echo "[i] Found storage controller ".get_class($controllers[(count($controllers)-1)])."\n";
}
}
if(count($controllers)==0)
die("[X] Error: You should define at least one storage controller in your inc/config.inc.php first");
if(in_array('sim',$argv))
{
echo "[!!!!] SIMULATION MODE. Nothing will be uploaded [!!!!] \n\n";
$sim = true;
}
else $sim = false;
$enc=false;
if(defined('ENCRYPTION_KEY') && ENCRYPTION_KEY)
{
$enc = new Encryption;
echo "[i] Encryption key found. Will encrypt on Storage controllers\n";
}
if(!in_array('p2',$argv))
{
echo "[i] PHASE 1\n";
echo " [P1] Files from Storage controllers will be downloaded if they don't exist localy\n";
sleep(1);
foreach($controllers as $contr)
{
echo " [P1] Collecting list of items from ".get_class($contr)."..\n";
$controllerfiles = $contr->getItems(true);
echo "\n done. Got ".count($controllerfiles)." files\n";
if($controllerfiles)
foreach($controllerfiles as $cfile)
{
if(endswith($cfile,'.enc'))
$hash = substr($cfile,0,-4);
else $hash = $cfile;
$filehashes[$contr][] = $hash;
if(!is_dir($dir.$hash) || !file_exists($dir.$hash.DS.$hash)) //file only on storage controller. Will download
{
echo " [P1] $hash is not on the Server but on ".get_class($contr)."\n";
if($enc && endswith($cfile,'.enc')) // if its encrypted and we can decrypt, then do it
{
if($sim!==true)
{
$contr->pullFile($cfile,ROOT.DS.'tmp'.DS.$cfile);
$enc->decryptFile(ROOT.DS.'tmp'.DS.$cfile, ROOT.DS.'tmp'.DS.$hash,base64_decode(ENCRYPTION_KEY));
storeFile(ROOT.DS.'tmp'.DS.$hash,$hash,true);
unlink(ROOT.DS.'tmp'.DS.$cfile);
}
}
else{ //otherwise just get the file
if($sim!==true){
$contr->pullFile($hash,ROOT.DS.'tmp'.DS.$hash);
storeFile(ROOT.DS.'tmp'.DS.$hash,$hash,true);
}
}
}
}
}
echo "\n ----------- END OF PHASE 1 -----------\n\n";
}
else echo "[i] Skipping Phase 1\n";
echo "[i] PHASE 2\n";
echo " [P2] Local files are synced to all storage controllers\n";
sleep(2);
echo " [P2] Looping through local files\n";
$dh = opendir($dir);
$localfiles = array();
$allhashes=0;$allsize=0;
$skips=0;$skipsize=0;
$copied=0;$copysize=0;
$errors=0;$errorsize=0;
$uploaded=0;$uploadsize=0;
while (false !== ($hash = readdir($dh))) {
if($hash=='.'||$hash=='..') continue;
$img = $dir.$hash.DS.$hash;
if(!file_exists($img)) continue;
$thissize = filesize($img);
if(!isExistingHash($hash))
continue;
$allhashes++;
$allsize+=$thissize;
$realhash = ($enc===false?$hash:$hash.'.enc');
foreach($controllers as $contr)
{
if( (count($filehashes[$contr]) > 0 && !in_array($hash,$filehashes[$contr])) || !$contr->hashExists($realhash) )
{
//if($sim!==true)
if(defined('ENCRYPTION_KEY') && ENCRYPTION_KEY && !$contr->hashExists($hash.'.enc')) //ok so we got an encryption key which means we'll upload the encrypted file
{
echo " [P2] Controller '".get_class($contr)."' doesn't have $hash. Encrypting and uploading.. ";
$encryptedfile = $img.'.enc';
if($sim!==true)
{
$enc->encryptFile($img,$encryptedfile,base64_decode(ENCRYPTION_KEY));
$uploadsize+=filesize($encryptedfile);
$contr->pushFile($encryptedfile,$hash.'.enc');
unlink($encryptedfile);
}
}
else
{
echo " [P2] Controller '".get_class($contr)."' doesn't have $hash. Uploading unencrypted.. ";
if($sim!==true)
$contr->pushFile($img,$hash);
$uploadsize+=$thissize;
}
echo "done\n";
$uploaded++;
}
}
}
closedir($dh);
echo "\n[i] Done\n";
echo "\n----------- STATS ----------\n\n";
echo " All files found:\t$allhashes\t".renderSize($allsize)."\n";
echo " Copied files:\t$copied\t".renderSize($copysize)."\n";
echo " Skipped files:\t$skips\t".renderSize($skipsize)."\n";
echo " Erroneous files:\t$errors\t".renderSize($errorsize)."\n";
echo "\n";