summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeena <spam@jeenaparadies.net>2015-07-23 00:32:01 +0200
committerJeena <spam@jeenaparadies.net>2015-07-24 20:57:54 +0200
commit85e80df0ba127936f9ba646ba00e25b37ddc2ec4 (patch)
tree3438827e96ff8b32cffeec79073a8c72c9f062e9
parent91307d4a362a8fab0022aba19ab74bec9cb46f8f (diff)
Adds possibility to post photos.
With this it is possible to post a photo note with a description, nothing more. It doesn't move the file in the file system, just posts it from the temp location to the users server. It also does validate for file size, content type and max upload size and shows the errors to the user. If everything goes according to plan the response from the users server is shown, together with a link with the posted photos URL.
-rw-r--r--composer.json3
-rw-r--r--composer.lock75
-rw-r--r--controllers/controllers.php61
-rw-r--r--lib/helpers.php85
-rw-r--r--views/layout.php1
-rw-r--r--views/photo.php38
6 files changed, 233 insertions, 30 deletions
diff --git a/composer.json b/composer.json
index cf2f57e..1156a2b 100644
--- a/composer.json
+++ b/composer.json
@@ -10,7 +10,8 @@
"mpratt/relativetime": ">=1.0",
"firebase/php-jwt": "dev-master",
"ruudk/twitter-oauth": "dev-master",
- "andreyco/instagram": "3.*"
+ "andreyco/instagram": "3.*",
+ "p3k/multipart": "*"
},
"autoload": {
"files": [
diff --git a/composer.lock b/composer.lock
index eb3dfc1..819c8ea 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1,9 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
- "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
],
- "hash": "f2f8fdb671b52ce22dc0a5e133f9a13d",
+ "hash": "561c25a6b782004d9b05656de5d67971",
"packages": [
{
"name": "andreyco/instagram",
@@ -51,27 +52,25 @@
{
"name": "firebase/php-jwt",
"version": "dev-master",
- "target-dir": "Firebase/PHP-JWT",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
- "reference": "83b8899cb73d85d648af93f37ec0ac89f4a5bbae"
+ "reference": "fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b8899cb73d85d648af93f37ec0ac89f4a5bbae",
- "reference": "83b8899cb73d85d648af93f37ec0ac89f4a5bbae",
+ "url": "https://api.github.com/repos/firebase/php-jwt/zipball/fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1",
+ "reference": "fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1",
"shasum": ""
},
"require": {
- "php": ">=5.2.0"
+ "php": ">=5.3.0"
},
"type": "library",
"autoload": {
- "classmap": [
- "Authentication/",
- "Exceptions/"
- ]
+ "psr-4": {
+ "Firebase\\JWT\\": "src"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -91,7 +90,7 @@
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
- "time": "2014-11-18 17:58:25"
+ "time": "2015-07-22 18:31:08"
},
{
"name": "indieauth/client",
@@ -365,16 +364,16 @@
},
{
"name": "mpratt/relativetime",
- "version": "1.0",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/mpratt/RelativeTime.git",
- "reference": "5dd7078d2bc830227c1f5a0081c68c323fb18555"
+ "reference": "219e6568fa3e7b181244f93be493fbab4c89c056"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mpratt/RelativeTime/zipball/5dd7078d2bc830227c1f5a0081c68c323fb18555",
- "reference": "5dd7078d2bc830227c1f5a0081c68c323fb18555",
+ "url": "https://api.github.com/repos/mpratt/RelativeTime/zipball/219e6568fa3e7b181244f93be493fbab4c89c056",
+ "reference": "219e6568fa3e7b181244f93be493fbab4c89c056",
"shasum": ""
},
"require": {
@@ -409,7 +408,43 @@
"time",
"time-ago"
],
- "time": "2013-09-23 22:51:48"
+ "time": "2015-05-28 14:13:23"
+ },
+ {
+ "name": "p3k/multipart",
+ "version": "0.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/aaronpk/php-multipart-encoder.git",
+ "reference": "f5400011b20046cebbdfed686d051fb2aa600a14"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/aaronpk/php-multipart-encoder/zipball/f5400011b20046cebbdfed686d051fb2aa600a14",
+ "reference": "f5400011b20046cebbdfed686d051fb2aa600a14",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">5.4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/p3k/Multipart.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache 2.0"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Parecki",
+ "homepage": "http://aaronparecki.com"
+ }
+ ],
+ "description": "Multipart Encoding Library",
+ "time": "2015-07-16 19:28:02"
},
{
"name": "ruudk/twitter-oauth",
@@ -486,12 +521,12 @@
"version": "2.2.0",
"source": {
"type": "git",
- "url": "https://github.com/codeguy/Slim.git",
+ "url": "https://github.com/slimphp/Slim.git",
"reference": "b8181de1112a1e2f565b40158b621c34ded38053"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/codeguy/Slim/zipball/b8181de1112a1e2f565b40158b621c34ded38053",
+ "url": "https://api.github.com/repos/slimphp/Slim/zipball/b8181de1112a1e2f565b40158b621c34ded38053",
"reference": "b8181de1112a1e2f565b40158b621c34ded38053",
"shasum": ""
},
@@ -533,6 +568,8 @@
"firebase/php-jwt": 20,
"ruudk/twitter-oauth": 20
},
+ "prefer-stable": false,
+ "prefer-lowest": false,
"platform": [],
"platform-dev": []
}
diff --git a/controllers/controllers.php b/controllers/controllers.php
index b444676..337f780 100644
--- a/controllers/controllers.php
+++ b/controllers/controllers.php
@@ -127,6 +127,19 @@ $app->get('/favorite', function() use($app) {
}
});
+$app->get('/photo', function() use($app) {
+ if($user=require_login($app)) {
+ $params = $app->request()->params();
+
+ $html = render('photo', array(
+ 'title' => 'New Photo',
+ 'note_content' => '',
+ 'authorizing' => false
+ ));
+ $app->response()->body($html);
+ }
+});
+
$app->get('/repost', function() use($app) {
if($user=require_login($app)) {
$params = $app->request()->params();
@@ -282,6 +295,20 @@ function create_favorite(&$user, $url) {
return $r;
}
+function create_photo(&$user, $params, $file) {
+ $error = validate_photo($file);
+
+ if(!$error) {
+ $file_path = $file['tmp_name'];
+ $micropub_request = array('content' => $params['note_content']);
+ $r = micropub_post_for_user($user, $micropub_request, $file_path);
+ } else {
+ $r = array('error' => $error);
+ }
+
+ return $r;
+}
+
function create_repost(&$user, $url) {
$micropub_request = array(
'repost-of' => $url
@@ -336,6 +363,40 @@ $app->post('/favorite', function() use($app) {
}
});
+$app->post('/photo', function() use($app) {
+ if($user=require_login($app)) {
+
+ // var_dump($app->request()->post());
+ //
+ // Since $app->request()->post() with multipart is always
+ // empty (bug in Slim?) We're using the raw $_POST here
+ // until this gets fixed.
+ // PHP empties everything in $_POST if the file upload size exceeds
+ // that is why we have to test if the variables exist first.
+
+ $note_content = isset($_POST['note_content']) ? $_POST['note_content'] : null;
+ $params = array('note_content' => $note_content);
+ $file = isset($_FILES['note_photo']) ? $_FILES['note_photo'] : null;
+
+ $r = create_photo($user, $params, $file);
+
+ // Populate the error if there was no location header.
+ if(empty($r['location']) && empty($r['error'])) {
+ $r['error'] = "No 'Location' header in response.";
+ }
+
+ $html = render('photo', array(
+ 'title' => 'Photo posted',
+ 'note_content' => $params['note_content'],
+ 'location' => (isset($r['location']) ? $r['location'] : null),
+ 'error' => (isset($r['error']) ? $r['error'] : null),
+ 'response' => (isset($r['response']) ? htmlspecialchars($r['response']) : null),
+ 'authorizing' => false
+ ));
+ $app->response()->body($html);
+ }
+});
+
$app->post('/repost', function() use($app) {
if($user=require_login($app)) {
$params = $app->request()->params();
diff --git a/lib/helpers.php b/lib/helpers.php
index 4f6b4c1..ad16e1b 100644
--- a/lib/helpers.php
+++ b/lib/helpers.php
@@ -70,9 +70,9 @@ function get_timezone($lat, $lng) {
return null;
}
-function micropub_post_for_user(&$user, $params) {
+function micropub_post_for_user(&$user, $params, $file_path = NULL) {
// Now send to the micropub endpoint
- $r = micropub_post($user->micropub_endpoint, $params, $user->micropub_access_token);
+ $r = micropub_post($user->micropub_endpoint, $params, $user->micropub_access_token, $file_path);
$user->last_micropub_response = substr(json_encode($r), 0, 1024);
$user->last_micropub_response_date = date('Y-m-d H:i:s');
@@ -90,21 +90,33 @@ function micropub_post_for_user(&$user, $params) {
return $r;
}
-function micropub_post($endpoint, $params, $access_token) {
+function micropub_post($endpoint, $params, $access_token, $file_path = NULL) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint);
- curl_setopt($ch, CURLOPT_HTTPHEADER, array(
- 'Authorization: Bearer ' . $access_token
- ));
curl_setopt($ch, CURLOPT_POST, true);
- $post = http_build_query(array_merge(array(
- 'h' => 'entry'
- ), $params));
- $post = preg_replace('/%5B[0-9]+%5D/', '%5B%5D', $post); // change [0] to []
+
+ $httpheaders = array('Authorization: Bearer ' . $access_token);
+ $params = array_merge(array('h' => 'entry'), $params);
+
+ if(!$file_path) {
+ $post = http_build_query($params);
+ $post = preg_replace('/%5B[0-9]+%5D/', '%5B%5D', $post); // change [0] to []
+ } else {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ $mimetype = finfo_file($finfo, $file_path);
+ $multipart = new p3k\Multipart();
+ $multipart->addArray($params);
+ $multipart->addFile('photo', $file_path, $mimetype);
+ $post = $multipart->data();
+ array_push($httpheaders, 'Content-Type: ' . $multipart->contentType());
+ }
+
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheaders);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
+
$response = curl_exec($ch);
$error = curl_error($ch);
$sent_headers = curl_getinfo($ch, CURLINFO_HEADER_OUT);
@@ -215,4 +227,57 @@ function instagram_client() {
));
}
+function validate_photo(&$file) {
+ try {
+ if ($_SERVER['REQUEST_METHOD'] == 'POST' && count($_POST) < 1 ) {
+ throw new RuntimeException('File upload size exceeded.');
+ }
+
+ // Undefined | Multiple Files | $_FILES Corruption Attack
+ // If this request falls under any of them, treat it invalid.
+ if (
+ !isset($file['error']) ||
+ is_array($file['error'])
+ ) {
+ throw new RuntimeException('Invalid parameters.');
+ }
+
+ // Check $file['error'] value.
+ switch ($file['error']) {
+ case UPLOAD_ERR_OK:
+ break;
+ case UPLOAD_ERR_NO_FILE:
+ throw new RuntimeException('No file sent.');
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ throw new RuntimeException('Exceeded filesize limit.');
+ default:
+ throw new RuntimeException('Unknown errors.');
+ }
+
+ // You should also check filesize here.
+ if ($file['size'] > 1000000) {
+ throw new RuntimeException('Exceeded filesize limit.');
+ }
+
+ // DO NOT TRUST $file['mime'] VALUE !!
+ // Check MIME Type by yourself.
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ if (false === $ext = array_search(
+ $finfo->file($file['tmp_name']),
+ array(
+ 'jpg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ ),
+ true
+ )) {
+ throw new RuntimeException('Invalid file format.');
+ }
+
+ } catch (RuntimeException $e) {
+
+ return $e->getMessage();
+ }
+} \ No newline at end of file
diff --git a/views/layout.php b/views/layout.php
index 072dea6..7c2dab9 100644
--- a/views/layout.php
+++ b/views/layout.php
@@ -66,6 +66,7 @@ if(property_exists($this, 'include_facebook')) {
<li><a href="/new">New Note</a></li>
<li><a href="/bookmark">Bookmark</a></li>
<li><a href="/favorite">Favorite</a></li>
+ <li><a href="/photo">Photo</a></li>
<? } ?>
<li><a href="/docs">Docs</a></li>
diff --git a/views/photo.php b/views/photo.php
new file mode 100644
index 0000000..364a55d
--- /dev/null
+++ b/views/photo.php
@@ -0,0 +1,38 @@
+ <div class="narrow">
+ <?= partial('partials/header') ?>
+
+ <form method="POST" role="form" style="margin-top: 20px;" id="note_form" enctype="multipart/form-data">
+
+ <div class="form-group">
+ <label for="note_photo"><code>photo</code></label>
+ <input type="file" name="note_photo" id="note_photo" accept="image/jpg,image/jpeg,image/gif,image/png">
+ <p class="help-block">Photo JPEG, GIF or PNG.</p>
+ </div>
+
+ <div class="form-group">
+ <label for="note_content"><code>content</code> (optional)</label>
+ <textarea name="note_content" id="note_content" value="" class="form-control" style="height: 4em;"><? if(isset($this->note_content)) echo $this->note_content ?></textarea>
+ </div>
+
+ <button class="btn btn-success" id="btn_post">Post</button>
+ </form>
+
+ <? if(!empty($this->location)): ?>
+ <div class="alert alert-success">
+ <strong>Success!</strong> Photo posted to: <em><a href="<?= $this->location ?>"><?= $this->location ?></a></em>
+ </div>
+ <? endif ?>
+
+ <? if(!empty($this->error)): ?>
+ <div class="alert alert-danger">
+ <strong>Error:</strong> <em><?= $this->error ?></em>
+ </div>
+ <? endif ?>
+
+ <? if(!empty($this->response)): ?>
+ <h4>Response:</h4>
+ <pre><?= $this->response ?></pre>
+ <? endif ?>
+
+
+ </div> \ No newline at end of file