Commit 162c6473 authored by Malcolm Blaney's avatar Malcolm Blaney

Refactored Reader module and SimplePie Parser to handle microformats

more cleanly. Some properties are now stored using Media RSS extension
so that they can be accessed as enclosures using SimplePie API. Also
moved custom lightbox code to Reader module. Image handler needs to be
the full url for Microsub clients. Other changes are mostly formatting
and a few small bug fixes.
parent 45d7d97f
Pipeline #34760507 passed with stage
in 1 minute and 24 seconds
......@@ -41,13 +41,13 @@ class Analytics extends Base {
$mysqli = connect_db();
$referer = isset($_SERVER['HTTP_REFERER']) ?
$mysqli->escape_string($_SERVER['HTTP_REFERER']) : '';
// The current time is converted to a timestamp on the hour.
$timestamp = strtotime(date('F j Y H:00:00').' UTC');
$query = 'INSERT INTO analytics VALUES ("'.$this->owner.'", '.
'"'.$this->user->page.'", '.$timestamp.', "'.$referer.'", 1) '.
'ON DUPLICATE KEY UPDATE counter = counter + 1';
// The current time is quantized to a timestamp on the hour.
$timestamp = strtotime(date('F j Y H:00:00') . ' UTC');
$query = 'INSERT INTO analytics VALUES ("' . $this->owner . '", ' .
'"' . $this->user->page . '", ' . $timestamp . ', "' . $referer . '", ' .
'1) ON DUPLICATE KEY UPDATE counter = counter + 1';
if (!$mysqli->query($query)) {
$this->Log('Analytics->Content: '.$mysqli->error);
$this->Log('Analytics->Content: ' . $mysqli->error);
}
$mysqli->close();
return false;
......@@ -143,10 +143,10 @@ class Analytics extends Base {
public function Referers($start, $end) {
$data = [];
$mysqli = connect_db();
$query = 'SELECT user, page, referer, SUM(counter) AS counter FROM '.
'analytics WHERE referer != "" AND timestamp >= '.$start.' AND '.
'timestamp <= '.$end.' GROUP BY user, page, referer ORDER BY counter '.
'DESC';
$query = 'SELECT user, page, referer, SUM(counter) AS counter FROM ' .
'analytics WHERE referer != "" AND timestamp >= ' . $start . ' AND ' .
'timestamp <= ' . $end . ' GROUP BY user, page, referer ORDER BY ' .
'counter DESC';
if ($result = $mysqli->query($query)) {
while ($analytics = $result->fetch_assoc()) {
$data[] = ['user' => $analytics['user'],
......@@ -157,7 +157,7 @@ class Analytics extends Base {
$result->close();
}
else {
$this->Log('Analytics->Referers: '.$mysqli->error);
$this->Log('Analytics->Referers: ' . $mysqli->error);
}
$mysqli->close();
return $data;
......@@ -166,8 +166,8 @@ class Analytics extends Base {
public function Total($start, $end) {
$total = 0;
$mysqli = connect_db();
$query = 'SELECT SUM(counter) AS counter FROM analytics WHERE '.
'timestamp >= '.$start.' AND timestamp <= '.$end;
$query = 'SELECT SUM(counter) AS counter FROM analytics WHERE ' .
'timestamp >= ' . $start . ' AND timestamp <= ' . $end;
if ($result = $mysqli->query($query)) {
if ($analytics = $result->fetch_assoc()) {
$total = (int)$analytics['counter'];
......@@ -175,7 +175,7 @@ class Analytics extends Base {
$result->close();
}
else {
$this->Log('Analytics->Total: '.$mysqli->error);
$this->Log('Analytics->Total: ' . $mysqli->error);
}
$mysqli->close();
return $total;
......
......@@ -83,6 +83,7 @@ class Grid extends Base {
'"",".slick-cell > input[type=text]","border","none"',
'"",".slick-cell > input[type=text]","width","100%"',
'"",".slick-cell > input[type=text]","height","100%"',
'"",".slick-cell > a","text-decoration","none"',
'"","button.long-text-save","float","right"'];
$this->AddSiteStyle($site_style);
}
......
......@@ -154,7 +154,10 @@ class Reader extends Base {
$feed = new SimplePie();
$feed->force_feed(true);
$feed->enable_order_by_date(false);
$feed->set_image_handler('/php/image.php');
$scheme = $this->user->config->Secure() ? 'https://' : 'http://';
$handler = $scheme . $this->user->config->ServerName() . '/php/image.php';
// The full image handler url is required for Microsub clients.
$feed->set_image_handler($handler);
$daily = $this->Run('12am');
$us_updated = [];
foreach ($this->FeedList() as $us_xml_url) {
......@@ -361,6 +364,7 @@ class Reader extends Base {
'content TEXT,' .
'author VARCHAR(200),' .
'category TEXT,' .
'enclosure TEXT, ' .
'permalink VARCHAR(250),' .
'guid VARCHAR(200) NOT NULL,' .
'timestamp INT(10) UNSIGNED NOT NULL,' .
......@@ -455,8 +459,7 @@ class Reader extends Base {
'"",".reader a","color","#666666"',
'"",".reader a:hover","color","#999999"',
'"",".reader","font-family","Verdana,Arial"',
'"",".reader-actions","padding-bottom","15px"',
'"",".reader-actions","margin-left","10px"',
'"",".reader-actions","margin","10px"',
'"",".reader-actions","clear","both"',
'"",".reader-actions a","margin","5px"',
'"","label[for=reader-toggle-import]","float","right"',
......@@ -527,7 +530,8 @@ class Reader extends Base {
public function Update() {
$mysqli = connect_db();
$query = 'ALTER TABLE reader_items MODIFY category TEXT';
$query = 'ALTER TABLE reader_items ADD COLUMN enclosure TEXT AFTER ' .
'category';
if (!$mysqli->query($query)) {
$this->Log('Reader->Update: ' . $mysqli->error);
}
......@@ -562,7 +566,10 @@ class Reader extends Base {
// to do feed discovery.
$feed = new SimplePie();
$feed->enable_order_by_date(false);
$feed->set_image_handler('/php/image.php');
$scheme = $this->user->config->Secure() ? 'https://' : 'http://';
$handler = $scheme . $this->user->config->ServerName() . '/php/image.php';
// The full image handler url is required for Microsub clients.
$feed->set_image_handler($handler);
$feed->set_feed_url($us_xml_url);
$feed->force_feed($force);
// get_all_discoverd_feeds doesn't work if caching is used.
......@@ -1016,14 +1023,16 @@ class Reader extends Base {
}
$key = $this->owner . '-' . $this->user->page;
// The same fields are selected in each of the queries used below.
$select_query = 'SELECT title, content, author, category, enclosure, ' .
'permalink, guid, timestamp, reader_items.xml_url, feed_title, ' .
'html_url, image_url, image_title, image_link FROM reader_items ' .
'LEFT JOIN reader_feeds ON reader_items.xml_url = reader_feeds.xml_url';
if ($us_update === 'newer') {
// This will run the first time a page is loaded.
if (!isset($_SESSION['reader-first'][$key])) {
$query = 'SELECT title, content, author, category, permalink, guid, ' .
'timestamp, reader_items.xml_url, feed_title, html_url, image_url, ' .
'image_title, image_link FROM reader_items LEFT JOIN reader_feeds ' .
'ON reader_items.xml_url = reader_feeds.xml_url WHERE ' .
$xml_url_query . ' ORDER BY timestamp DESC LIMIT ' . $item_count;
$query = $select_query . ' WHERE ' . $xml_url_query .
' ORDER BY timestamp DESC LIMIT ' . $item_count;
return $this->GetContent($query, $item_count, true, $microsub);
}
......@@ -1052,12 +1061,8 @@ class Reader extends Base {
return ['content' => false, 'more' => false];
}
$query = 'SELECT title, content, author, category, permalink, guid, ' .
'timestamp, reader_items.xml_url, feed_title, html_url, image_url, ' .
'image_title, image_link FROM reader_items LEFT JOIN reader_feeds ' .
'ON reader_items.xml_url = reader_feeds.xml_url WHERE ' .
'timestamp > ' . $timestamp . ' AND (' . $xml_url_query . ') ' .
'ORDER BY timestamp DESC';
$query = $select_query . ' WHERE timestamp > ' . $timestamp .
' AND (' . $xml_url_query . ') ORDER BY timestamp DESC';
return $this->GetContent($query, 0);
}
......@@ -1067,10 +1072,7 @@ class Reader extends Base {
if (!isset($_SESSION['reader-last'][$key])) {
return ['error' => 'There was a problem loading more items.'];
}
$query = 'SELECT title, content, author, category, permalink, guid, ' .
'timestamp, reader_items.xml_url, feed_title, html_url, image_url, ' .
'image_title, image_link FROM reader_items LEFT JOIN reader_feeds ON ' .
'reader_items.xml_url = reader_feeds.xml_url WHERE timestamp < ' .
$query = $select_query . ' WHERE timestamp < ' .
$_SESSION['reader-last'][$key] . ' AND (' . $xml_url_query . ') ' .
'ORDER BY timestamp DESC LIMIT ' . $item_count;
return $this->GetContent($query, $item_count, false);
......@@ -1088,34 +1090,64 @@ class Reader extends Base {
if ($author_photo === '') {
$author_photo = $item['image_url'];
}
$category = '';
// Categories can be comma separated values.
if (strpos($item['category'], ',') !== false) {
$category_list = [];
foreach (explode(',', $item['category']) as $tag) {
if (!in_array(trim($tag), $category_list)) {
$category_list[] = trim($tag);
$category .= category_markup($tag);
}
}
}
else {
$category .= category_markup($item['category']);
}
$category_list = json_decode($item['category'], true);
$enclosure_list = json_decode($item['enclosure'], true);
if ($microsub) {
return ['type' => 'entry',
'published' => date(DATE_ATOM, $item['timestamp']),
'url' => $permalink,
'url' => $permalink, 'uid' => $item['guid'],
'author' => ['type' => 'card', 'name' => $author_name,
'url' => $author_url, 'photo' => $author_photo],
'name' => $item['title'],
'category' => $category_list,
'photo' => $enclosure_list['photo'],
'audio' => $enclosure_list['audio'],
'video' => $enclosure_list['video'],
'content' => ['html' => $item['content']], '_id' => 'home'];
}
$title = $item['title'];
$content = content_markup($item['content']);
$date = date('j F Y, g:ia', $item['timestamp']);
if (count($enclosure_list['photo']) > 0) {
$photo_list = [];
foreach ($enclosure_list['photo'] as $photo) {
if (strpos($content, $photo) === false) {
$photo_list[] = $photo;
}
}
if (count($photo_list) === 0) {
// All photos were found to be duplicated in content, so assume this
// is a photo post and display the lightbox. Add a special wrapper
// around the existing content so those photos can be hidden.
$content = '<div class="photo-hidden">' . $content . '</div>';
$photo_list = $enclosure_list['photo'];
}
$count = count($photo_list);
if ($count > 1) {
// When there's more than one photo show the first two and use a
// lightbox. Need a permanent, unique name for the image set, but
// don't have anything unique except for photo urls, so use that.
$image_set_id = preg_replace('/[[:^alnum:]]/', '', $photo_list[0]);
$content .= '<p class="photo-list">';
for ($i = 0; $i < $count; $i++) {
$hidden = $i <= 1 ? '' : 'class="hidden" ';
$content .= '<a href="' . $photo_list[$i] . '" ' . $hidden .
'data-lightbox="image-set-' . $image_set_id . '">' .
'<img src="' . $photo_list[$i] . '"></a>';
}
$content .= '<br><b>' . $count . ' photos</b></p>';
}
else if ($count == 1) {
$content .= '<p><img src="' . $photo_list[0] . '"></p>';
}
}
foreach ($enclosure_list['audio'] as $audio) {
$content .= '<audio controls src="' . $audio . '"></audio>';
}
foreach ($enclosure_list['video'] as $video) {
$content .= '<video controls src="' . $video . '"></video>';
}
$date = date('j F g:ia', $item['timestamp']);
$reader_format = $title === '' || $config['hide-titles'] ?
'reader-format-no-title' : 'reader-format-with-title';
$author = '';
......@@ -1125,6 +1157,10 @@ class Reader extends Base {
}
$author .= '<a href="' . $author_url . '" class="author-name">' .
$author_name . '</a>';
$category = '';
foreach ($category_list as $tag) {
$category .= category_markup($tag);
}
if ($category !== '') {
$category = '<span class="reader-tag-label">tags: </span>' . $category;
}
......@@ -1132,10 +1168,11 @@ class Reader extends Base {
$date = '<a href="' . $permalink . '" class="permalink">' . $date .'</a>';
}
// If the content is too long, show an excerpt and add a link so that
// the real content can be expanded.
if (strlen($content) > 600) {
$start = substr($content, 0, 400);
$content = '<span class="content-excerpt">' . strip_tags($start) .
// the real content can be expanded. Also sometimes the content isn't
// overly long it's just full of markup so strip tags first.
$start = strip_tags($content);
if (strlen($start) > 600) {
$content = '<span class="content-excerpt">' . substr($start, 0, 400) .
'... <a href="#" class="read-more">read more</a></span>' .
'<span class="real-content hidden">' . $content . '</span>';
}
......@@ -1773,6 +1810,10 @@ class Reader extends Base {
}
private function SaveItem($item, $xml_url, $purifier) {
$us_permalink = $item->get_permalink();
// If the item doesn't have a permalink then skip it.
if (!$us_permalink) return false;
$us_title = '';
// Twitter feed is a special case, there are no titles on Twitter but
// there is usually a modified version of the content in the item's title.
......@@ -1796,95 +1837,110 @@ class Reader extends Base {
else if (strlen($us_title) > 100) {
// This assumes that long titles are probably the same as the content,
// but the comparison failed. In this case show a shortened version.
$us_title = substr($us_title, 0, 50) . '...';
$us_title = substr($us_title, 0, 40) . '...';
}
}
$updated = false;
$mysqli = connect_db();
$permalink = $mysqli->escape_string($us_permalink);
$title = $mysqli->escape_string($us_title);
$content = $mysqli->escape_string($purifier->purify($us_content));
$us_author = '';
if ($item_author = $item->get_author()) {
$us_author = $item_author->get_email();
if (!$us_author) {
$us_author = $item_author->get_name();
}
if (!$us_author) {
$us_author = $item_author->get_link();
}
}
// html isn't escaped for author and categories to support h-card.
$us_author = $purifier->purify(htmlspecialchars_decode($us_author));
// Try parsing author as microformats which will store it in nickname cache.
$mf = Mf2\parse($us_author);
foreach ($mf['items'] as $mf_item) {
$us_author_name = parse_hcard($mf_item, true);
if ($us_author_name !== '') {
$us_author = $us_author_name;
break;
}
}
$author = $mysqli->escape_string($us_author);
$us_category = '';
$categories = $item->get_categories();
if ($categories) {
foreach ($categories as $category) {
if (!$category) continue;
if ($us_category !== '') $us_category .= ', ';
$us_category .= htmlspecialchars_decode($category->get_label());
}
}
$category = $mysqli->escape_string($purifier->purify($us_category));
$updated = false;
// If the item doesn't have a permalink then skip it.
if ($permalink = $mysqli->escape_string($item->get_permalink())) {
$guid = $mysqli->escape_string($item->get_id());
// guid's must be less than 100 characters as they are used as a primary
// key in the reader_items table, so use md5 to keep them unique.
if (strlen($guid) > 100) $guid = md5($guid);
$query = '';
// If both title and content are empty, delete the item from the feed.
// Allow checking by permalink too in this case because we let SimplePie
// create new guids so ours won't match anymore.
if ($title === '' && $content === '') {
$query = 'DELETE FROM reader_items WHERE (guid = "' . $guid . '" OR ' .
'permalink = "' . $permalink . '") AND xml_url = "' . $xml_url . '"';
}
else {
// If this item doesn't have a date or the date is in the future, set
// it to the current time to avoid having it pinned at the top of the
// reader. Also if an item has an old timestamp but hasn't been saved
// yet, give it the current time so it's not buried. Don't want to see
// all items when subscribing to a new feed, so don't change the
// timestamp when older than 48 hours.
$timestamp = strtotime($item->get_date());
if (!$timestamp || $timestamp > time() ||
($timestamp < strtotime('-1 hour') &&
$timestamp > strtotime('-48 hours'))) {
$timestamp = time();
$guid = $mysqli->escape_string($item->get_id());
// guid's must be less than 100 characters as they are used as a primary
// key in the reader_items table, so use md5 to keep them unique.
if (strlen($guid) > 100) $guid = md5($guid);
$query = '';
// If both title and content are empty, delete the item from the feed.
// Allow checking by permalink too in this case because we let SimplePie
// create new guids so ours won't match anymore.
if ($title === '' && $content === '') {
$query = 'DELETE FROM reader_items WHERE (guid = "' . $guid . '" OR ' .
'permalink = "' . $permalink . '") AND xml_url = "' . $xml_url . '"';
}
else {
$us_author = '';
if ($item_author = $item->get_author()) {
$us_author = $item_author->get_email();
if (!$us_author) {
$us_author = $item_author->get_name();
}
if (!$us_author) {
$us_author = $item_author->get_link();
}
$query = 'INSERT INTO reader_items VALUES ("' . $title . '", ' .
'"' . $content . '", "' . $author . '", "' . $category . '", ' .
'"' . $permalink . '", "' . $guid . '", ' . $timestamp . ', ' .
'"' . $xml_url . '") ON DUPLICATE KEY UPDATE ' .
'title = "' . $title . '", content = "' . $content . '", ' .
'author = "' . $author . '", category = "' . $category . '", ' .
'permalink = "' . $permalink . '"';
}
if ($mysqli->query($query)) {
$updated = $mysqli->affected_rows !== 0;
// html isn't escaped for author and categories to support h-card.
$us_author = $purifier->purify(htmlspecialchars_decode($us_author));
// Try parsing author as microformats which will store it in the nickname
// cache.
$mf = Mf2\parse($us_author);
foreach ($mf['items'] as $mf_item) {
$us_author_name = parse_hcard($mf_item, true);
if ($us_author_name !== '') {
$us_author = $us_author_name;
break;
}
}
else {
$this->Log('Reader->SaveItem 1: ' . $mysqli->error);
$author = $mysqli->escape_string($us_author);
$us_category_list = [];
if ($all_categories = $item->get_categories()) {
foreach ($all_categories as $category) {
if (!$category) continue;
// Allow html in categories but purify.
$us_label =
$purifier->purify(htmlspecialchars_decode($category->get_label()));
$us_category_list[] = $us_label;
}
}
// SimplePie creates it's own guid from the data if one wasn't explicitly
// set, so old items with the same permalink need to be removed.
$query = 'DELETE FROM reader_items WHERE ' .
'permalink = "' . $permalink . '" AND xml_url = "' . $xml_url . '" ' .
'AND guid != "' . $guid . '"';
if (!$mysqli->query($query)) {
$this->Log('Reader->SaveItem 2: ' . $mysqli->error);
$category = $mysqli->escape_string(json_encode($us_category_list));
// Store enclosures grouped together as photo, audio, video.
$photo = [];
$audio = [];
$video = [];
if ($all_enclosures = $item->get_enclosures()) {
foreach ($all_enclosures as $enclosure) {
if (!$link = $enclosure->get_link()) continue;
$type = $enclosure->get_type();
if (strpos($type, 'image/') === 0) $photo[] = $link;
else if (strpos($type, 'audio/') === 0) $audio[] = $link;
else if (strpos($type, 'video/') === 0) $video[] = $link;
}
}
$media = ['photo' => $photo, 'audio' => $audio, 'video' => $video];
$enclosure = $mysqli->escape_string(json_encode($media));
// If this item doesn't have a date or the date is in the future, set
// it to the current time to avoid having it pinned at the top of the
// reader. Also if an item has an old timestamp but hasn't been saved
// yet, give it the current time so it's not buried. Don't want to see
// all items when subscribing to a new feed, so don't change the
// timestamp when older than 48 hours.
$timestamp = strtotime($item->get_date());
if (!$timestamp || $timestamp > time() ||
($timestamp < strtotime('-1 hour') &&
$timestamp > strtotime('-48 hours'))) {
$timestamp = time();
}
$query = 'INSERT INTO reader_items VALUES ("' . $title . '", ' .
'"' . $content . '", "' . $author . '", "' . $category . '", ' .
'"' . $enclosure . '", "' . $permalink . '", "' . $guid . '", ' .
$timestamp . ', "' . $xml_url . '") ON DUPLICATE KEY UPDATE ' .
'title = "' . $title . '", content = "' . $content . '", ' .
'author = "' . $author . '", category = "' . $category . '", ' .
'enclosure = "' . $enclosure . '", permalink = "' . $permalink . '"';
}
if ($mysqli->query($query)) {
$updated = $mysqli->affected_rows !== 0;
}
else {
$this->Log('Reader->SaveItem 1: ' . $mysqli->error);
}
// SimplePie creates it's own guid from the data if one wasn't explicitly
// set, so old items with the same permalink need to be removed.
$query = 'DELETE FROM reader_items WHERE permalink = "' . $permalink . '" '.
'AND xml_url = "' . $xml_url . '" AND guid != "' . $guid . '"';
if (!$mysqli->query($query)) {
$this->Log('Reader->SaveItem 2: ' . $mysqli->error);
}
$mysqli->close();
return $updated;
......@@ -1961,7 +2017,10 @@ class Reader extends Base {
$feed = new SimplePie();
$feed->set_feed_url($us_xml_url);
$feed->enable_order_by_date(false);
$feed->set_image_handler('/php/image.php');
$scheme = $this->user->config->Secure() ? 'https://' : 'http://';
$handler = $scheme . $this->user->config->ServerName() . '/php/image.php';
// The full image handler url is required for Microsub clients.
$feed->set_image_handler($handler);
$feed->force_feed(true);
$feed->set_cache_duration(0);
$feed->set_raw_data($data);
......
<?php
// Dobrado Content Management System
// Copyright (C) 2017 Malcolm Blaney
// Copyright (C) 2018 Malcolm Blaney
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
......@@ -30,8 +30,8 @@ class Viewanalytics extends Base {
// end dates in local time to UTC. Want to display the data with local
// timestamps however, so also calculate the offset to local time.
$offset = strtotime('UTC') - strtotime('now');
$start = strtotime(date('F j Y 00:00:00', $start).' UTC');
$end = strtotime(date('F j Y 23:59:59', $end).' UTC');
$start = strtotime(date('F j Y 00:00:00', $start) . ' UTC');
$end = strtotime(date('F j Y 23:59:59', $end) . ' UTC');
if (!$start || !$end || $start > $end) {
return ['error' => 'Start and End dates required.'];
}
......@@ -70,18 +70,18 @@ class Viewanalytics extends Base {
}
public function Content($id) {
return '<div class="viewanalytics-total-search">'.
'Enter dates to view total visits:'.
'<div class="form-spacing">'.
'<label for="viewanalytics-total-start">Start:</label>'.
'<input id="viewanalytics-total-start" maxlength="50">'.
'</div>'.
'<div class="form-spacing">'.
'<label for="viewanalytics-total-end">End:</label>'.
'<input id="viewanalytics-total-end" maxlength="50">'.
'</div>'.
'<button id="viewanalytics-total-button">submit</button>'.
'</div>'.
return '<form id="viewanalytics-total-search">' .
'Enter dates to view total visits:' .
'<div class="form-spacing">' .
'<label for="viewanalytics-total-start">Start:</label>' .
'<input id="viewanalytics-total-start" maxlength="50">' .
'</div>' .
'<div class="form-spacing">' .
'<label for="viewanalytics-total-end">End:</label>' .
'<input id="viewanalytics-total-end" maxlength="50">' .
'</div>' .
'<button id="viewanalytics-total-button">submit</button>' .
'</form>' .
'<div class="viewanalytics-total-graph"></div>';
}
......@@ -109,6 +109,9 @@ class Viewanalytics extends Base {
// Need to call AppendScript here if module uses javascript.
// Note that the module is only available when logged in.
$this->AppendScript($path, 'dobrado.viewanalytics.js', false);
$site_style = ['"",".viewanalytics","font-family","Verdana,Arial"',
'"","#viewanalytics-total-search label","width","4em"',
'"","#viewanalytics-total-button","margin-left","3.9em"'];
}
public function Placement() {
......
......@@ -27,9 +27,10 @@
if(!this.dobrado.viewanalytics){dobrado.viewanalytics={};}
(function(){'use strict';var analytics={};var totalGraphId='';var refererGrid=null;var refererGridId='';$(function(){if($('.viewanalytics').length===0){return;}
$('#viewanalytics-total-start').datepicker({dateFormat:dobrado.dateFormat}).val('');$('#viewanalytics-total-end').datepicker({dateFormat:dobrado.dateFormat}).val('');$('#viewanalytics-total-button').button().click(view);if($('.graph').length===1){var graphId='#'+$('.graph').attr('id');$(graphId+' .graph-area').each(function(index){if(index===0){totalGraphId=$(this).attr('id');$('#'+totalGraphId).parent().hide();}});}
if($('.grid').length===1){refererGridId='#'+$('.grid').attr('id');var columns=[{id:"user",name:"User",field:"user",width:100,sortable:true},{id:"page",name:"Page",field:"page",width:150,sortable:true},{id:"referer",name:"Referer",field:"referer",width:400,sortable:true},{id:"counter",name:"#",field:"counter",width:50,sortable:true}];var options={autoHeight:true,forceFitColumns:true};refererGrid=dobrado.grid.instance(refererGridId,[],columns,options);refererGrid.onSort.subscribe(function(e,args){analytics.referers.sort(function(row1,row2){var field=args.sortCol.field;var sign=args.sortAsc?1:-1;var value1=row1[field];var value2=row2[field];if(value1===value2){return 0;}
if($('.grid').length===1){refererGridId='#'+$('.grid').attr('id');var columns=[{id:"user",name:"User",field:"user",width:100,sortable:true},{id:"page",name:"Page",field:"page",width:150,sortable:true},{id:"referer",name:"Referer",field:"referer",width:400,sortable:true,formatter:refererFormatter},{id:"counter",name:"#",field:"counter",width:50,sortable:true}];var options={autoHeight:true,forceFitColumns:true};refererGrid=dobrado.grid.instance(refererGridId,[],columns,options);refererGrid.onSort.subscribe(function(e,args){analytics.referers.sort(function(row1,row2){var field=args.sortCol.field;var sign=args.sortAsc?1:-1;var value1=row1[field];var value2=row2[field];if(value1===value2){return 0;}
if(value1>value2){return sign;}
else{return sign* -1;}});refererGrid.invalidate();});}
setTimeout(view,500);});function view(){var start=parseInt($.datepicker.formatDate('@',$('#viewanalytics-total-start').datepicker('getDate')),10);var end=parseInt($.datepicker.formatDate('@',$('#viewanalytics-total-end').datepicker('getDate')),10);dobrado.log('Loading data.','info');$.post('/php/request.php',{request:'viewanalytics',start:start,end:end,url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'viewanalytics')){return;}
setTimeout(view,500);});function refererFormatter(row,cell,value,columnDef,dataContext){if(value==='')return'';return'<a href="'+value+'">'+value+'</a>';}
function view(){var start=parseInt($.datepicker.formatDate('@',$('#viewanalytics-total-start').datepicker('getDate')),10);var end=parseInt($.datepicker.formatDate('@',$('#viewanalytics-total-end').datepicker('getDate')),10);dobrado.log('Loading data.','info');$.post('/php/request.php',{request:'viewanalytics',start:start,end:end,url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'viewanalytics')){return;}
analytics=JSON.parse(response);if(totalGraphId){$('#'+totalGraphId).html('').parent().hide();$('#'+totalGraphId).parent().appendTo('.viewanalytics-total-graph');if(analytics.data){$('#'+totalGraphId).parent().show();dobrado.graph.loadData(totalGraphId,analytics.data,analytics.series);}}
if(refererGrid){refererGrid.setData(analytics.referers);refererGrid.updateRowCount();refererGrid.render();}});}}());
\ No newline at end of file
if(refererGrid){refererGrid.setData(analytics.referers);refererGrid.updateRowCount();refererGrid.render();}});return false;}}());
\ No newline at end of file
......@@ -63,7 +63,8 @@ if (!this.dobrado.viewanalytics) {
{ id : "page", name: "Page", field: "page",
width: 150, sortable: true },
{ id : "referer", name: "Referer", field: "referer",
width: 400, sortable: true },
width: 400, sortable: true,
formatter: refererFormatter },
{ id : "counter", name: "#", field: "counter",
width: 50, sortable: true }];
var options = { autoHeight: true, forceFitColumns: true };
......@@ -91,6 +92,11 @@ if (!this.dobrado.viewanalytics) {
setTimeout(view, 500);
});
function refererFormatter(row, cell, value, columnDef, dataContext) {
if (value === '') return '';
return '<a href="' + value + '">' + value + '</a>';
}
function view() {
var start = parseInt($.datepicker.formatDate('@',
$('#viewanalytics-total-start').datepicker('getDate')), 10);
......@@ -123,6 +129,7 @@ if (!this.dobrado.viewanalytics) {
refererGrid.render();
}
});
return false;
}
}());
......@@ -72,7 +72,7 @@ if($.inArray(target,targetList[status])===-1){targetList[status].push(target);ur
else{statusQuery[status]=url;}}}});$.each(statusQuery,function(status,query){var href=config[status].replace('{url}',query);var xhr=createCORSRequest(href);if(xhr){xhr.onload=function(){if(xhr.responseText){var state=JSON.parse(xhr.responseText);updateActions(state);}};xhr.withCredentials=true;xhr.send();}});}
function openDialog(){var config=null;if(dobrado.localStorage&&localStorage.indieConfig){config=JSON.parse(localStorage.indieConfig);}
if(config&&actionSet(config)){return;}
$('.indie-config-info').html(indieConfigInfo).show();var text='<div><p>This page displays <b>web actions</b> that <i>link'+'back to your own website</i>, so that you can own your response.'+'<br>(You can then send a webmention to notify this site.)</p>'+'<p>If you have a website that handles web actions, you can provide '+'an address to find your web action config here:'+'<form><div class="form-spacing">'+'<label for="indie-action-handler">Address:</label>'+'<input id="indie-action-handler" type="text">'+'<button id="indie-action-submit"">submit</button>'+'</div><span id="indie-action-info"></span></form></p>'+'<p>Instead of using your own site, you can also use Quill, Twitter '+'or Facebook for web actions by clicking one of the buttons below.'+'</p>'+'<button id="indie-action-quill">Quill</button>'+'<button id="indie-action-twitter">Twitter</button>'+'<button id="indie-action-facebook">Facebook</button></div>';if($('#indie-config-dialog').length===0){$(text).attr('id','indie-config-dialog').appendTo('body');}
$('.indie-config-info').html(indieConfigInfo).show();$('indie-action').each(function(){$(this).data('checked',false);});var text='<div><p>This page displays <b>web actions</b> that <i>link'+'back to your own website</i>, so that you can own your response.'+'<br>(You can then send a webmention to notify this site.)</p>'+'<p>If you have a website that handles web actions, you can provide '+'an address to find your web action config here:'+'<form><div class="form-spacing">'+'<label for="indie-action-handler">Address:</label>'+'<input id="indie-action-handler" type="text">'+'<button id="indie-action-submit"">submit</button>'+'</div><span id="indie-action-info"></span></form></p>'+'<p>Instead of using your own site, you can also use Quill, Twitter '+'or Facebook for web actions by clicking one of the buttons below.'+'</p>'+'<button id="indie-action-quill">Quill</button>'+'<button id="indie-action-twitter">Twitter</button>'+'<button id="indie-action-facebook">Facebook</button></div>';if($('#indie-config-dialog').length===0){$(text).attr('id','indie-config-dialog').appendTo('body');}
$('#indie-config-dialog').dialog({show:true,width:500,position:{my:'top',at:'top+50',of:window},title:'Web Actions',create:dobrado.fixedDialog});$('#indie-action-submit').button().click(function(){$('#indie-action-info').html('Checking...');$.post('/php/webaction.php',{url:$('#indie-action-handler').val(),token:dobrado.token},function(response){if(actionSet(response)){localStorage.indieConfig=JSON.stringify(response);handleConfig();$('#indie-action-info').html('<b>config found.</b>');setTimeout(function(){$('#indie-config-dialog').dialog('close');$('#indie-config-dialog').remove();},2000);}
else{$('#indie-action-info').html('<i>config was not found using '+'the address given.</i>');}});return false;});$('#indie-action-quill').button().click(function(){localStorage.indieConfig=JSON.stringify({like:'https://quill.p3k.io/favorite?url={url}',repost:'https://quill.p3k.io/repost?url={url}',reply:'https://quill.p3k.io/new?reply={url}',});$('#indie-config-dialog').dialog('close');$('#indie-config-dialog').remove();handleConfig();});$('#indie-action-twitter').button().click(function(){localStorage.indieConfig=JSON.stringify({repost:'https://twitter.com/intent/tweet?url={url}'});$('#indie-config-dialog').dialog('close');$('#indie-config-dialog').remove();handleConfig();});$('#indie-action-facebook').button().click(function(){localStorage.indieConfig=JSON.stringify({repost:'https://www.facebook.com/sharer/sharer.php?u={url}'});$('#indie-config-dialog').dialog('close');$('#indie-config-dialog').remove();handleConfig();});}
function webActionSettings(){var config=null;if(dobrado.localStorage&&localStorage.indieConfig){config=JSON.parse(localStorage.indieConfig);}
......
......@@ -346,8 +346,9 @@ if (!this.dobrado) {
return;
}
// Reset the description on the page.
// Reset the description on the page and unset indie-action checks.
$('.indie-config-info').html(indieConfigInfo).show();
$('indie-action').each(function() { $(this).data('checked', false); });
// Then load a dialog to explain what web actions are.
var text = '<div><p>This page displays <b>web actions</b> that <i>link' +
'back to your own website</i>, so that you can own your response.' +
......
......@@ -174,7 +174,14 @@ if ($scope === '' && $response_type === 'code') {
$scope = 'create';
}
echo '<p>' . $app_details . ' wants to log you in as <a href="' . $me . '">' .
echo '<!DOCTYPE html>' . "\n" .
'<meta charset="utf-8">' . "\n" .
'<meta name="viewport" content="width=device-width">' . "\n" .
'<html><head><title>Authorisation requested</title>' . "\n" .
'<style> p { line-height: 50px; } img { width: 50px; float: left; ' .
'margin-right: 10px; } button { margin-left: 60px }' .
'</style></head>' .
'<p>' . $app_details . ' wants to log you in as <a href="' . $me . '">' .
$me . '</a></p>' .
'<form action="/php/auth_endpoint.php" method="post">' .
'<input type="hidden" name="me" value="' . $me . '">' .
......@@ -186,4 +193,5 @@ echo '<p>' . $app_details . ' wants to log you in as <a href="' . $me . '">' .
'<input type="hidden" name="token" ' .
'value="' . $_SESSION['auth-endpoint'] . '">' .
'<button>Continue</button>' .
'</form>' . $redirect_info;
\ No newline at end of file
'</form>' . $redirect_info .
'</body></html>';
\ No newline at end of file
......@@ -131,7 +131,10 @@ function discover_endpoint($url, $rels) {
// Use SimplePie to cache images.
include_once 'autoloader.php';
$simple_pie = new SimplePie();
$simple_pie->set_image_handler('/php/image.php');
$scheme = $this->user->config->Secure() ? 'https://' : 'http://';
$handler = $scheme . $this->user->config->ServerName() . '/php/image.php';
// The full image handler url is required for Microsub clients.
$simple_pie->set_image_handler($handler);
$simple_pie->init();
// Use the url without the scheme for more lenient matching.
$domain = $url;
......@@ -321,7 +324,11 @@ function parse_hcard($author, $name_only = false) {
// Use SimplePie to cache images.
include_once 'autoloader.php';
$simple_pie = new SimplePie();
$simple_pie->set_image_handler('/php/image.php');
$scheme = $this->user->config->Secure() ? 'https://' : 'http://';
$handler = $scheme . $this->user->config->ServerName() .
'/php/image.php';
// The full image handler url is required for Microsub clients.
$simple_pie->set_image_handler($handler);
$simple_pie->init();
$us_photo = $simple_pie->sanitize($us_photo, SIMPLEPIE_CONSTRUCT_IRI,
'', true);
......
......@@ -513,6 +513,7 @@ class SimplePie_Parser
if (in_array('h-entry', $entry['type']) ||
in_array('h-cite', $entry['type'])) {
$item = array();
$media = array();
$title = '';
$description = '';
if (isset($entry['properties']['url'][0])) {
......@@ -569,10 +570,10 @@ class SimplePie_Parser
$use_content = true;
if (isset($entry['properties']['summary'][0])) {
// Use summary as content if there are any unrecognized properties.
$known_properties = ['like-of', 'repost-of', 'in-reply-to', 'url',
'author', 'name', 'uid', 'category', 'published',
'summary', 'content', 'photo', 'comment', 'like',
'repost'];
$known_properties =
array('like-of', 'repost-of', 'in-reply-to', 'url', 'author',
'name', 'uid', 'category', 'published', 'summary', 'content',
'photo', 'comment', 'like', 'repost');
foreach ($entry['properties'] as $name => $property) {