Browse Source

Merge pull request #22 from jeena/photo

Adds possibility to post photos.
pull/25/head
Aaron Parecki 6 years ago
parent
commit
afa182bc12
6 changed files with 233 additions and 30 deletions
  1. +2
    -1
      composer.json
  2. +56
    -19
      composer.lock
  3. +61
    -0
      controllers/controllers.php
  4. +75
    -10
      lib/helpers.php
  5. +1
    -0
      views/layout.php
  6. +38
    -0
      views/photo.php

+ 2
- 1
composer.json View File

@ -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": [

+ 56
- 19
composer.lock View File

@ -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": []
}

+ 61
- 0
controllers/controllers.php View File

@ -130,6 +130,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();
@ -286,6 +299,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
@ -340,6 +367,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();

+ 75
- 10
lib/helpers.php View File

@ -74,9 +74,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');
@ -94,21 +94,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);
@ -219,4 +231,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();
}
}

+ 1
- 0
views/layout.php View File

@ -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>

+ 38
- 0
views/photo.php View File

@ -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>

Loading…
Cancel
Save