Commit 3dc7cf27 authored by Malcolm Blaney's avatar Malcolm Blaney

Add support for Micropub Media Endpoint to Browser module, and

Micropub query config support to auth.php. Improved parse_hcard
in SimplePie Parser to also cache photos in h-feeds, and
parse_hcard helper function in microformats.php will now add photos
from reposts to the nickname cache. Invoice and Purchase modules
now operate from the assumption that dates for a given day can have
timestamps anywhere between 00:00:00 and 23:59:59 on the day given.
Fixed a bug in Purchase module that was using a 12 hour window to
check purchases rather than following the new rule for timestamps.
Stock module bug made items that were available to purchase
unavailable from the supplier, which meant they no longer showed up
by default on the stock page. Other changes are small UI improvements.
parent 50633ad8
Pipeline #36803455 passed with stage
in 1 minute and 24 seconds
......@@ -78,9 +78,9 @@ class Browser extends Base {
continue;
}
if (preg_match('/^(.+)\.(.+)$/', $filename, $matches)) {
$name = $matches[1];
$type = $matches[2];
if (preg_match('/^(.+)\.(.+)$/', $filename, $match)) {
$name = $match[1];
$type = $match[2];
$thumbnail = '';
$updated = '';
if (in_array(strtolower($type), ['gif', 'jpeg', 'jpg', 'png'])) {
......@@ -178,9 +178,9 @@ class Browser extends Base {
private function RemoveFile() {
$regex = '/^([a-z0-9_-]{1,200})\.([a-z0-9]{1,10})/i';
$us_file = basename($_POST['file']);
if (preg_match($regex, $us_file, $matches)) {
$name = $matches[1];
$type = $matches[2];
if (preg_match($regex, $us_file, $match)) {
$name = $match[1];
$type = $match[2];
unlink($this->PublicDirectory($name . '.' . $type));
if (in_array(strtolower($type), ['gif', 'jpeg', 'jpg', 'png'])) {
unlink($this->PublicDirectory($name . '_thumb.' . $type));
......@@ -195,10 +195,10 @@ class Browser extends Base {
$handle = popen('/usr/bin/du -sm ' . $this->PublicDirectory(), 'r');
$size = fgets($handle);
pclose($handle);
if (!preg_match('/^([0-9]+)/', $size, $matches)) {
if (!preg_match('/^([0-9]+)/', $size, $match)) {
return ['error' => 'Could not check upload directory'];
}
$size = $matches[1];
$size = $match[1];
if ((int)$size > $this->user->config->MaxUpload()) {
return ['error' => 'Upload directory full.'];
}
......@@ -211,18 +211,18 @@ class Browser extends Base {
// Replace spaces in the uploaded file name.
$filename = preg_replace('/ /', '_', basename($_FILES['upload']['name']));
$regex = '/^([a-z0-9_-]{1,200})\.([a-z0-9]{1,10})$/i';
if (!preg_match($regex, $filename, $matches)) {
if (!preg_match($regex, $filename, $match)) {
return ['error' => 'Filename does not have correct format.'];
}
$path = $this->PublicDirectory($filename);
if (file_exists($path)) {
return ['error' => 'A file with that name already exists.'];
}
$name = $matches[1];
$type = $matches[2];
$name = $match[1];
$type = $match[2];
// Whitelist all file extensions.
$allowed = ['gif', 'jpeg', 'jpg', 'png', 'ogg', 'mp3', 'ogv', 'webm',
'mp4', 'pdf'];
$allowed = ['gif', 'jpeg', 'jpg', 'png', 'aac', 'mpeg', 'oga', 'ogg', 'wav',
'mp3', 'ogv', 'webm', 'mp4', 'mov', 'pdf'];
if (!in_array(strtolower($type), $allowed)) {
return ['error' => 'File type not allowed.'];
}
......@@ -230,7 +230,14 @@ class Browser extends Base {
if (!move_uploaded_file($tmp, $path)) {
return ['error' => 'File: ' . $filename . ' was not uploaded.'];
}
// If this user has a media endpoint configured transfer the file to it.
if (isset($this->user->settings['micropub']['config'])) {
$config = json_decode($this->user->settings['micropub']['config'], true);
if (isset($config['media-endpont'])) {
return $this->TransferMedia($path, $config['media-endpont']);
}
}
$name .= '_thumb.' . $type;
$html_path = $this->user->name === 'admin' ? '/public/' :
'/' . $this->user->name . '/public/';
......@@ -277,16 +284,84 @@ class Browser extends Base {
$this->ImageContent($thumbnail, $html_path . $filename)];
}
private function ImageContent($thumbnail, $filename) {
private function ImageContent($thumbnail, $filename, $local = true) {
// Display rotate and remove buttons on local thumbnails only.
$buttons = $local ?
'<button class="rotate-left hidden">rotate left</button>' .
'<button class="rotate-right hidden">rotate right</button>' .
'<button class="remove hidden">remove</button>' : '';
return '<span class="thumbnail hidden">' . $thumbnail .
'<span class="filename hidden">' . $filename . '</span><br>' .
'<button class="select hidden">select</button>' .
'<button class="rotate-left hidden">rotate left</button>' .
'<button class="rotate-right hidden">rotate right</button>' .
'<button class="remove hidden">remove</button>' .
'<button class="select hidden">select</button>' . $buttons .
'</span>';
}
private function TransferMedia($path, $endpoint) {
$error = '';
$location = '';
$uid = uniqid();
$mime_type = strtolower(mime_content_type($path));
$curl_headers = ['Authorization: Bearer ' .
$this->user->settings['micropub']['token'],
'Content-Type: multipart/form-data; boundary=' . $uid];
$post_fields = '--' . $uid . "\r\n" .
'Content-Type: ' . $mime_type . "\r\n" .
"Content-Transfer-Encoding: binary\r\n" .
'Content-Disposition: form-data; name="file"; ' .
'filename="' . basename($path) . "\r\n\r\n" .
file_get_contents($path) . "\r\n" .
'--' . $uid . '--';
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_setopt($ch, CURLOPT_HEADERFUNCTION,
function($ch, $header) use(&$location) {
if (preg_match('/^Location:(.+)$/', $header, $match)) {
$location = $match[1];
}
return strlen($header);
});
$body = curl_exec($ch);
if (curl_errno($ch) === 0) {
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code !== 201) {
$error = 'Error saving to media endpoint.';
$this->Log('Browser->TransferMedia 1: Error posting to ' . $endpoint .
"\nHTTP code: " . $http_code . "\nBody: " . $body);
}
}
else {
$error = 'Error connection to media endpoint.';
$this->Log('Browser->TransferMedia 2: Error connecting to ' . $endpoint .
"\nCurl error: " . curl_error($ch));
}
curl_close($ch);
unlink($path);
if ($error !== '') return ['error' => $error];
if ($location === '') {
return ['error' => 'Media endpoint did not return location.'];
}
if (strpos($location, 'http') !== 0) {
return ['error' => 'Media endpoint did not return a URL.'];
}
$thumbnail = '';
if (in_array($mime_type, ['image/gif', 'image/jpeg', 'image/png'])) {
$thumbnail = '<img title="' . $location . '" src="' . $location . '">';
}
else {
$thumbnail = '<span title="' . $location . '">' . $mime_type . '</span>';
}
return ['content' => $this->ImageContent($thumbnail, $location, false)];
}
private function ResizeImage($new_width, $new_height,
$old_width, $old_height,
$type, $new_path, $old_path = '') {
......@@ -332,9 +407,9 @@ class Browser extends Base {
if ($_POST['direction'] === 'right') $angle = 270;
$file = basename($_POST['file']);
$regex = '/^([a-z0-9_-]{1,200})\.([a-z0-9]{1,10})/i';
if (preg_match($regex, $file, $matches)) {
$name = $matches[1];
$type = $matches[2];
if (preg_match($regex, $file, $match)) {
$name = $match[1];
$type = $match[2];
if (!in_array(strtolower($type), ['gif', 'jpeg', 'jpg', 'png'])) {
return ['error' => 'Format not supported.'];
}
......
......@@ -80,6 +80,7 @@ class Grid extends Base {
'"",".slick-headerrow-column.ui-state-default","padding",' .
'"1px"',
'"",".slick-cell.selected","background-color","#fdffaf"',
'"",".slick-cell > input[type=text]","padding","0"',
'"",".slick-cell > input[type=text]","border","none"',
'"",".slick-cell > input[type=text]","width","100%"',
'"",".slick-cell > input[type=text]","height","100%"',
......
......@@ -707,6 +707,8 @@ class Groupwizard extends Base {
'"", ".groupwizard textarea", "height", "100px"',
'"", "label[for=groupwizard-invoice-subject]", ' .
'"width", "16em"',
'"", "label[for=groupwizard-invoice-no-purchase-subject]", '.
'"width", "16em"',
'"", "label[for=groupwizard-invoice-sender]", ' .
'"width", "16em"',
'"", "label[for=groupwizard-invoice-sender-name]", ' .
......
......@@ -728,12 +728,12 @@ class Invoice extends Base {
else if ($invoice_day_count === 1) {
// Special case if only one day is specified, set the start time to the
// start of today.
$start = strtotime('0:00');
$end = time();
$start = strtotime('00:00:00');
$end = strtotime('23:59:59');
}
else {
$start = strtotime('-' . $invoice_day_count . ' days');
$end = time();
$end = strtotime('23:59:59');
}
// TODO: Check if tax should be shown for the group and call AllTaxable.
$purchase_totals = $purchase->AllTotals($start, $end, $organisation);
......@@ -924,12 +924,12 @@ class Invoice extends Base {
else if ($invoice_day_count === 1) {
// Special case if only one day is specified, set the start time to the
// start of today.
$start = strtotime('0:00');
$end = $invoice_date;
$start = strtotime('00:00:00');
$end = strtotime('23:59:59');
}
else {
$start = strtotime('-' . $invoice_day_count . ' days');
$end = $invoice_date;
$end = strtotime('23:59:59');
}
$message = $this->FormatInvoice($user, $first, $last, $invoice_number,
$reference, $start, $end, $purchase_value,
......@@ -1353,8 +1353,9 @@ class Invoice extends Base {
$xero->Factory('SendInvoice', $xml);
}
// Next display user info via substitutions for the group.
$message .= $this->FormatInvoiceAfterPurchase($user, $end, $reference,
$invoice_number, $surcharge);
$message .=
$this->FormatInvoiceAfterPurchase($user, $invoice_date, $reference,
$invoice_number, $surcharge);
// Next pre order information is displayed, if in use.
if ($pre_order_available) {
if ($purchase_value < 0.01) {
......@@ -1969,8 +1970,8 @@ class Invoice extends Base {
$username = $mysqli->escape_string($_POST['selectedUser']);
$invoice_date = $mysqli->escape_string($_POST['invoiceDate']);
$invoice_number = 0;
$start = strtotime($invoice_date . ' 0:00');
$end = strtotime($invoice_date . ' 24:00');
$start = strtotime($invoice_date . ' 00:00:00');
$end = strtotime($invoice_date . ' 23:59:59');
$query = 'SELECT invoice_number FROM invoice WHERE ' .
'user = "' . $username . '" AND timestamp >= ' . $start . ' AND ' .
'timestamp <= ' . $end;
......
......@@ -2075,7 +2075,7 @@ class Purchase extends Base {
$us_data = json_decode($_POST['data'], true);
$us_user_index = json_decode($_POST['userIndex'], true);
$us_new_sales = json_decode($_POST['newSales'], true);
$start = strtotime(date('F j Y 00:00:00'));
$start = strtotime('00:00:00');
$end = $start + 86399;
$current = $this->AllData($start, $end, false, false, false);
$payment = new Payment($this->user, $this->owner);
......@@ -2671,7 +2671,7 @@ class Purchase extends Base {
$object['products'] = $stock->AvailableProducts();
// Otherwise in normal purchasing mode, only show purchases from today's
// date onwards, as only these can be changed in UpdateData.
$start = strtotime(date('F j Y 00:00:00'));
$start = strtotime('00:00:00');
$end = 0;
// An end timestamp is required when pre-order is true, otherwise
// orders for next week will be shown when purchasing this week.
......@@ -2884,6 +2884,7 @@ class Purchase extends Base {
$stock = new Stock($this->user, $this->owner);
$mysqli = connect_db();
$products = [];
$elapsed = time() - strtotime('00:00:00');
$tomorrow = time() + 86400;
$values = '';
// Create a list of items that need updating.
......@@ -2923,8 +2924,8 @@ class Purchase extends Base {
$price . ', ' . $base_price . ', "' . $this->user->group . '", ' .
'"' . $this->user->name . '")';
// Delete the old entry if it has a timestamp in the last 12 hours.
if ($new_date > $old_date && $new_date - $old_date < 43200) {
// Delete the old entry if it has a timestamp from today.
if ($new_date > $old_date && $new_date - $old_date < $elapsed) {
// Find the old quantity for this purchase to increase stock.
// Need to look at a timestamp range due to rounding by 1ms.
$query = 'SELECT quantity, timestamp FROM purchase WHERE ' .
......
......@@ -608,10 +608,14 @@ class Reader extends Base {
$checked = $i === 0 ? ' checked="checked"' : '';
$discovered_id = 'reader-discovered-feed-' . $i;
$discovered_url = $all_feeds[$i]->url;
$short_url = strlen($discovered_url) <= 50 ?
$discovered_url : substr($discovered_url, 0, 47) . '...';
$discovered_link = '<a href="' . $discovered_url . '">' . $short_url .
'</a>';
$discovered_title = $this->FeedTitle($all_feeds[$i]->body);
$display = $discovered_title === '' ? $discovered_url :
$display = $discovered_title === '' ? $discovered_link :
$discovered_title . ' <span class="reader-discovered-feed-url">[' .
$discovered_url . ']</span>';
$discovered_link . ']</span>';
$discovered .= '<div class="form-spacing">' .
'<input type="checkbox" id="' . $discovered_id . '"' .
'value="' . $discovered_url . '"' . $checked . '>' .
......
......@@ -208,12 +208,12 @@ class Stock extends Base {
$cart_column = '';
if ($this->Substitute('stock-cart') === 'true') {
$cart = '<div class="form-spacing hidden">' .
'<label for="stock-cart-input">Copy to Cart:</label>' .
'<label for="stock-cart-input">Sync to Cart:</label>' .
'<input id="stock-cart-input" type="checkbox">' .
'</div>';
$cart_column = '<input type="checkbox" id="stock-column-cart">' .
'<label for="stock-column-cart">Cart Status</label>';
$checkbox_options .= '<option value="cart">Copy to Cart</option>';
'<label for="stock-column-cart">Sync to Cart</label>';
$checkbox_options .= '<option value="cart">Sync to Cart</option>';
}
$checkbox_options .= '<option value="hidden">Hidden</option>';
......@@ -397,7 +397,7 @@ class Stock extends Base {
$this->user = new User($this->owner, $group, $timezone);
$category_query = '';
// Allow the update to be done for only a limited list of categories.
// Note that templates are html encoded so need to decode here.
// Note that templates are html encoded so decode them here.
$us_categories = $this->Substitute('stock-update-categories');
if ($us_categories !== '') {
$us_category_list =
......@@ -2269,7 +2269,7 @@ class Stock extends Base {
$available_query = '';
$purchase = new Purchase($this->user, $this->owner);
// Check if the user is choosing to change the product name. All previous
// purchases also need to be updated in this case.
// purchases are also updated in this case.
if ($field === 'name' && $product !== $value) {
$us_name = trim($_POST['value']);
if ($value === '') {
......@@ -2503,7 +2503,7 @@ class Stock extends Base {
}
}
$mysqli->close();
// If the last group was removed, need to revert back to all groups.
// If the last group was removed, revert back to all groups.
$this->CheckSupplyGroup($user);
}
......@@ -2848,6 +2848,13 @@ class Stock extends Base {
if (!$mysqli->query($query)) {
$this->Log('Stock->ImportData 11: ' . $mysqli->error);
}
// Make sure that items currently available to purchase aren't made
// unavailable from the supplier.
$query = 'UPDATE stock SET supplier_available = 1 WHERE ' .
'purchase_available = 1 AND user = "' . $user . '"';
if (!$mysqli->query($query)) {
$this->Log('Stock->ImportData 12: ' . $mysqli->error);
}
}
$mysqli->close();
$result = $this->AllData($new_available);
......
......@@ -59,8 +59,8 @@ return'<img src="'+value+'" style="height:20px;width:auto;">';}
function filter(item,args){for(var columnId in args.columnFilters){if(columnId&&args.columnFilters[columnId]!==''){var column=args.columns[args.index(columnId)];var input=args.columnFilters[columnId].toLowerCase();var field=item[column.field].toString().toLowerCase();if(input.indexOf('*')===0){if(field.indexOf(input.substr(1))===-1){return false;}}
else if(field.indexOf(input)!==0){return false;}}}
return true;}
function fixHeaderRow(){var currentScroll=$(this).scrollTop();if(previousScroll<=headerPosition&&currentScroll>headerPosition){headerRow.addClass('fixed-style');headerRow.css('width',$(stockGridId).width()+'px');if($('.control').css('position')==='absolute'){headerRow.css('top','0');}}
else if(previousScroll>headerPosition&&currentScroll<=headerPosition){headerRow.removeClass('fixed-style');headerRow.css('width','100%');if($('.control').css('position')==='absolute'){headerRow.css('top',headerTop);}}
function fixHeaderRow(){var currentScroll=$(this).scrollTop();if(previousScroll<=headerPosition&&currentScroll>headerPosition){headerRow.addClass('fixed-style');headerRow.css('width',$(stockGridId).width()+'px');if($('.control').css('position')==='static'){headerRow.css('top','0');}}
else if(previousScroll>headerPosition&&currentScroll<=headerPosition){headerRow.removeClass('fixed-style');headerRow.css('width','100%');if($('.control').css('position')==='static'){headerRow.css('top',headerTop);}}
previousScroll=currentScroll;}
var priceName='Price';var availableName='Members';var availableToolTip='Available to Members';if(stock.orderAvailable){availableName='Order';availableToolTip='Available to Order';}
if(stock.wholesalePercent||stock.retailPercent){priceName='Cost';}
......
......@@ -546,7 +546,7 @@ if (!this.dobrado.stock) {
// When the control bar scrolls with the page, header row should be
// fixed at the top of the page, otherwise the default css rule will
// position the header row under the control bar.
if ($('.control').css('position') === 'absolute') {
if ($('.control').css('position') === 'static') {
headerRow.css('top', '0');
}
}
......@@ -556,7 +556,7 @@ if (!this.dobrado.stock) {
// Reset the original width rule so wide grid option can still be used.
headerRow.css('width', '100%');
// Only need to reset the css rule for top when it's applied above.
if ($('.control').css('position') === 'absolute') {
if ($('.control').css('position') === 'static') {
headerRow.css('top', headerTop);
}
}
......
......@@ -71,7 +71,8 @@ else if(name==='east'){if(right!==$('.right').width()){difference=$('.right').wi
else if(name==='west'){if(left!==$('.left').width()){difference=$('.left').width()-left;left=$('.left').width();saveLayout('left');}}}
if(dobrado.layoutMode){dobrado.layoutMode=false;$('.sortable > *').css('cursor','default');$('.sortable').sortable('option','disabled',true);$('.ui-layout-resizer').css('display','none');$('.ui-layout-toggler').css('display','none');}
else{dobrado.layoutMode=true;$('.sortable > *').css('cursor','move');$('.sortable').sortable('option','disabled',false);$('.ui-layout-resizer').css('display','block');$('.ui-layout-toggler').css('display','block');if(!main){var height=$('.main').height();if(height<400){height=400;}
$('.main').css('height',height);main=$('.main').layout({east:{size:right},west:{size:left},closable:false,onresize:resize});}}}
$('.main').css('height',height);var width=$('.main').width();if(width<400){width=400;}
$('.main').css('width',width);main=$('.main').layout({east:{size:right},west:{size:left},closable:false,onresize:resize});}}}
function copy(){dobrado.hideOtherMenu('copy-menu-wrapper');dobrado.toggleMenu('copy-menu-wrapper');$('body').one('click',hideMenu);return false;}
function tools(){if(dobrado.editorLoading||$('.extended').dialog('isOpen')===true){if($('#control-tools').is(':checked')){$('#control-tools').prop('checked',false);}
else{$('#control-tools').prop('checked',true);}
......
......@@ -479,12 +479,17 @@ if (!this.dobrado.control) {
$('.ui-layout-resizer').css('display', 'block');
$('.ui-layout-toggler').css('display', 'block');
if (!main) {
// Make sure the main div has a minimum height, and set in css.
// Make sure the main div has a minimum height and width, set in css.
var height = $('.main').height();
if (height < 400) {
height = 400;
}
$('.main').css('height', height);
var width = $('.main').width();
if (width < 400) {
width = 400;
}
$('.main').css('width', width);
main = $('.main').layout({ east: { size: right },
west: { size: left },
closable: false,
......
......@@ -17,6 +17,37 @@
session_start();
function micropub_config($endpoint, $token) {
$config = '{}';
$endpoint .= strpos($endpoint, '?') === false ? '?q=config' : '&q=config';
$curl_headers = ['Authorization: Bearer ' . $token,
'Accept: application/json'];
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
curl_setopt($ch, CURLOPT_ENCODING, '');
curl_setopt($ch, CURLOPT_HEADER, false);
$body = curl_exec($ch);
if (curl_errno($ch) === 0) {
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code === 200) {
$config = $body;
}
else {
log_db('micropub_config 1: Error getting ' . $endpoint .
"\nHTTP code: " . $http_code . "\nBody: " . $body);
}
}
else {
log_db('micropub_config 2: Error connecting to ' . $endpoint .
"\nCurl error: " . curl_error($ch));
}
curl_close($ch);
return $config;
}
function verify_code($endpoint, $post_fields, $token = false) {
$check_me = '';
$ch = curl_init($endpoint);
......@@ -44,21 +75,21 @@ function verify_code($endpoint, $post_fields, $token = false) {
}
}
if ($check_me === '') {
log_db('auth.php 1: \'me\' property not found at ' . $endpoint .
log_db('verify_code 1: \'me\' property not found at ' . $endpoint .
"\nBody: " . $body);
}
else if ($token && !isset($_SESSION['access-token'])) {
log_db('auth.php 2: Access token not found at ' . $endpoint .
log_db('verify_code 2: Access token not found at ' . $endpoint .
"\nBody: " . $body);
}
}
else {
log_db('auth.php 3: Error posting to ' . $endpoint .
log_db('verify_code 3: Error posting to ' . $endpoint .
"\nHTTP code: " . $http_code . "\nBody: " . $body);
}
}
else {
log_db('auth.php 4: Error connecting to ' . $endpoint .
log_db('verify_code 4: Error connecting to ' . $endpoint .
"\nCurl error: " . curl_error($ch));
}
curl_close($ch);
......@@ -155,7 +186,7 @@ if ($result = $mysqli->query($query)) {
$result->close();
}
else {
log_db('auth.php 5: ' . $mysqli->error);
log_db('auth.php 1: ' . $mysqli->error);
}
if (!$exists) {
$password = bin2hex(openssl_random_pseudo_bytes(8));
......@@ -166,12 +197,12 @@ if (!$exists) {
'registration_time) VALUES ("' . $domain . '", "' . $system_group . '", ' .
'"' . $password_hash . '", 1, ' . time() . ')';
if (!$mysqli->query($query)) {
log_db('auth.php 6: ' . $mysqli->error);
log_db('auth.php 2: ' . $mysqli->error);
}
$query = 'INSERT INTO group_names VALUES ' .
'("admin", "' . $system_group . '", "' . $domain . '", 0)';
if (!$mysqli->query($query)) {
log_db('auth.php 7: ' . $mysqli->error);
log_db('auth.php 3: ' . $mysqli->error);
}
mkdir('../' . $domain . '/public', 0755, true);
}
......@@ -183,7 +214,7 @@ if (isset($_SESSION['access-token'])) {
'"micropub", "token", "' . $token . '") ON DUPLICATE KEY UPDATE ' .
'value = "' . $token . '"';
if (!$mysqli->query($query)) {
log_db('auth.php 8: ' . $mysqli->error);
log_db('auth.php 4: ' . $mysqli->error);
}
if (isset($_SESSION['micropub-endpoint'])) {
$endpoint = $mysqli->escape_string($_SESSION['micropub-endpoint']);
......@@ -191,7 +222,15 @@ if (isset($_SESSION['access-token'])) {
'"micropub", "endpoint", "' . $endpoint . '") ON DUPLICATE KEY UPDATE ' .
'value = "' . $endpoint . '"';
if (!$mysqli->query($query)) {
log_db('auth.php 9: ' . $mysqli->error);
log_db('auth.php 5: ' . $mysqli->error);
}
// Also fetch and store the micropub config.
$config = $mysqli->escape_string(micropub_config($endpoint, $token));
$query = 'INSERT INTO settings VALUES ("' . $domain . '", ' .
'"micropub", "config", "' . $config . '") ON DUPLICATE KEY UPDATE ' .
'value = "' . $config . '"';
if (!$mysqli->query($query)) {
log_db('auth.php 6: ' . $mysqli->error);
}
}
}
......@@ -201,7 +240,7 @@ else {
$query = 'DELETE FROM settings WHERE user = "' . $domain . '" ' .
'AND label = "micropub" AND (name = "token" OR name = "endpoint")';
if (!$mysqli->query($query)) {
log_db('auth.php 10: ' . $mysqli->error);
log_db('auth.php 7: ' . $mysqli->error);
}
}
$mysqli->close();
......
......@@ -432,6 +432,7 @@ function create_site_style() {
'"",".ui-tabs-nav li a","padding","0.3em 0.6em"',
'"",".ui-accordion-header a","padding","0.3em"',
'"",".ui-dialog-content","padding","0.5em 0"',
'"",".ui-spinner input[type=text]","border","none"',
'"",".ui-selectmenu-menu","position","fixed"',
'"",".ui-menu .ui-state-active","font-weight","normal"',
'"",".horizontal-menu:after","clear","both"',
......
......@@ -318,6 +318,7 @@ function parse_hcard($author, $name_only = false) {
$us_author_photo = '';
$us_author_url = '';
$us_repost_name = '';
$us_repost_photo = '';
$us_repost_url = '';
if (isset($author['type']) && in_array('h-card', $author['type'])) {
......@@ -336,7 +337,16 @@ function parse_hcard($author, $name_only = false) {
$us_photo = $simple_pie->sanitize($us_photo, SIMPLEPIE_CONSTRUCT_IRI,
'', true);
// img tag is stored to match Detail module for local accounts.
$us_author_photo = '<img class="thumb u-photo" src="' . $us_photo .'">';
$us_author_photo = '<img class="thumb u-photo" ' .
'src="' . $us_photo . '">';
if (isset($author['properties']['photo'][1])) {
$us_repost_photo = $author['properties']['photo'][1];
if ($us_repost_photo !== '') {
$us_repost_photo = $simple_pie->sanitize($us_repost_photo,
SIMPLEPIE_CONSTRUCT_IRI,
'', true);
}
}
}
}
if (isset($author['properties']['url'][0])) {
......@@ -373,10 +383,15 @@ function parse_hcard($author, $name_only = false) {
$us_repost_name =
str_replace(',', '', $author['properties']['name'][1]);
$repost_name = $mysqli->escape_string($us_repost_name);
$repost_photo = $mysqli->escape_string($us_repost_photo);
$repost_url = $mysqli->escape_string($us_repost_url);
// Don't replace the photo if not found on this h-card.
$update_photo_query = $repost_photo === '' ? '' :
', photo = "' . $repost_photo . '"';
$query = 'INSERT INTO nickname VALUES ("' . $repost_name . '", ' .
'"' . $repost_url . '", "", "", 0) ON DUPLICATE KEY UPDATE ' .
'name = "' . $repost_name . '"';
'"' . $repost_url . '", "' . $repost_photo . '", "", 0) ' .
'ON DUPLICATE KEY UPDATE name = "' . $repost_name . '"' .
$update_photo_query;
if (!$mysqli->query($query)) {
log_db('microformats->parse_hcard 2: ' . $mysqli->error);
}
......
......@@ -35,6 +35,9 @@ function style($selector, $rules) {
$style = $selector . " {\n";
foreach ($rules as $property => $value) {
if ($property === 'position' && $value === 'sticky') {
$style .= " position : -webkit-sticky;\n";
}
$style .= ' ' . $property . ' : ' . $value . ";\n";
}
$style .= "}\n";
......
......@@ -416,37 +416,57 @@ class SimplePie_Parser
}
private function parse_hcard($data, $category = false) {
if (!isset($data['type']) ||
!in_array('h-card', $data['type']) ||
!isset($data['properties']['url'][0])) {
return isset($data['value']) ? $data['value'] : '';
}
$name = '';
$link = '';
$photo = '';
$repost = '';
// Check if h-card is set and pass that information on in the link.
if (isset($data['type']) && in_array('h-card', $data['type'])) {
if (isset($data['properties']['name'][0])) {
$name = $data['properties']['name'][0];
$repost_name = '';
$repost_photo = '';
if (isset($data['properties']['name'][0])) {
$name = $data['properties']['name'][0];
}
if (isset($data['properties']['photo'][0])) {
$photo =
'<img class="u-photo" src="' . $data['properties']['photo'][0] . '">';
}
if (isset($data['properties']['name'][1])) {
// When there's a second name property assume this is a repost, but need
// to be careful with the photo property. If there's only one photo we
// can't be sure who it should apply to so just ignore it.
$repost_name = $data['properties']['name'][1];
if ($photo !== '' && isset($data['properties']['photo'][1])) {
$repost_photo = $data['properties']['photo'][1];
}
if (isset($data['properties']['name'][1])) {
$repost = $data['properties']['name'][1];
else {
$photo = '';
}
if (isset($data['properties']['url'][0])) {
$link = $data['properties']['url'][0];
if ($name === '') {
$name = $link;
}
else {
// can't have commas in categories.
$name = str_replace(',', '', $name);
}
if ($repost !== '' && isset($data['properties']['url'][1])) {
$repost = '<a class="p-name u-url" ' .
'href="' . $data['properties']['url'][1] . '">' . $repost . '</a>';
}
$person_tag = $category ? '<span class="person-tag"></span>' : '';
return '<span class="h-card"><a class="p-name u-url" ' .
'href="' . $link . '">' . $person_tag . $name . '</a> ' .
$repost . '</span>';
}
$link = $data['properties']['url'][0];
if ($name === '') {
$name = $link;
}
else {
// can't have commas in categories.
$name = str_replace(',', '', $name);
}
if ($repost_name !== '' && isset($data['properties']['url'][1])) {
if ($repost_photo !== '') {
$repost = '<img class="u-photo" src="' . $repost_photo . '">';
}
$repost .= '<a class="p-name u-url" ' .
'href="' . $data['properties']['url'][1] . '">' . $repost_name . '</a>';
}
return isset($data['value']) ? $data['value'] : '';
$person_tag = $category ? '<span class="person-tag"></span>' : '';
return '<span class="h-card"><a class="p-name u-url" ' .
'href="' . $link . '">' . $person_tag . $name . '</a> ' .
$repost . '</span>';
}
private function parse_microformats(&$data, $url) {
......