2016-04-10 17:34:07 +02:00
|
|
|
<?php
|
|
|
|
|
2018-12-04 00:13:42 +01:00
|
|
|
namespace Shaarli\Netscape;
|
|
|
|
|
|
|
|
use DateTime;
|
|
|
|
use DateTimeZone;
|
|
|
|
use Exception;
|
2017-02-09 20:54:56 +01:00
|
|
|
use Psr\Log\LogLevel;
|
2018-12-03 01:10:39 +01:00
|
|
|
use Shaarli\Bookmark\LinkDB;
|
2017-03-10 18:49:53 +01:00
|
|
|
use Shaarli\Config\ConfigManager;
|
2018-12-02 23:24:58 +01:00
|
|
|
use Shaarli\History;
|
2017-02-09 20:54:56 +01:00
|
|
|
use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
|
|
|
|
use Katzgrau\KLogger\Logger;
|
|
|
|
|
2016-04-10 17:34:07 +02:00
|
|
|
/**
|
|
|
|
* Utilities to import and export bookmarks using the Netscape format
|
2017-02-09 20:54:56 +01:00
|
|
|
* TODO: Not static, use a container.
|
2016-04-10 17:34:07 +02:00
|
|
|
*/
|
|
|
|
class NetscapeBookmarkUtils
|
|
|
|
{
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Filters links and adds Netscape-formatted fields
|
|
|
|
*
|
|
|
|
* Added fields:
|
|
|
|
* - timestamp link addition date, using the Unix epoch format
|
|
|
|
* - taglist comma-separated tag list
|
|
|
|
*
|
2016-05-05 19:22:06 +02:00
|
|
|
* @param LinkDB $linkDb Link datastore
|
|
|
|
* @param string $selection Which links to export: (all|private|public)
|
|
|
|
* @param bool $prependNoteUrl Prepend note permalinks with the server's URL
|
|
|
|
* @param string $indexUrl Absolute URL of the Shaarli index page
|
2016-04-10 17:34:07 +02:00
|
|
|
*
|
|
|
|
* @throws Exception Invalid export selection
|
|
|
|
*
|
|
|
|
* @return array The links to be exported, with additional fields
|
|
|
|
*/
|
2016-05-05 19:22:06 +02:00
|
|
|
public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl)
|
2016-04-10 17:34:07 +02:00
|
|
|
{
|
|
|
|
// see tpl/export.html for possible values
|
2018-12-04 00:13:42 +01:00
|
|
|
if (!in_array($selection, array('all', 'public', 'private'))) {
|
|
|
|
throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"');
|
2016-04-10 17:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$bookmarkLinks = array();
|
|
|
|
foreach ($linkDb as $link) {
|
|
|
|
if ($link['private'] != 0 && $selection == 'public') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ($link['private'] == 0 && $selection == 'private') {
|
|
|
|
continue;
|
|
|
|
}
|
2016-11-28 16:16:44 +01:00
|
|
|
$date = $link['created'];
|
2016-04-10 17:34:07 +02:00
|
|
|
$link['timestamp'] = $date->getTimestamp();
|
|
|
|
$link['taglist'] = str_replace(' ', ',', $link['tags']);
|
2016-05-05 19:22:06 +02:00
|
|
|
|
|
|
|
if (startsWith($link['url'], '?') && $prependNoteUrl) {
|
|
|
|
$link['url'] = $indexUrl . $link['url'];
|
|
|
|
}
|
|
|
|
|
2016-04-10 17:34:07 +02:00
|
|
|
$bookmarkLinks[] = $link;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $bookmarkLinks;
|
|
|
|
}
|
2016-07-28 22:54:33 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Generates an import status summary
|
|
|
|
*
|
|
|
|
* @param string $filename name of the file to import
|
|
|
|
* @param int $filesize size of the file to import
|
|
|
|
* @param int $importCount how many links were imported
|
|
|
|
* @param int $overwriteCount how many links were overwritten
|
|
|
|
* @param int $skipCount how many links were skipped
|
2017-10-07 16:40:16 +02:00
|
|
|
* @param int $duration how many seconds did the import take
|
2016-07-28 22:54:33 +02:00
|
|
|
*
|
|
|
|
* @return string Summary of the bookmark import status
|
|
|
|
*/
|
|
|
|
private static function importStatus(
|
|
|
|
$filename,
|
|
|
|
$filesize,
|
2018-10-13 00:19:03 +02:00
|
|
|
$importCount = 0,
|
|
|
|
$overwriteCount = 0,
|
|
|
|
$skipCount = 0,
|
|
|
|
$duration = 0
|
|
|
|
) {
|
2017-05-09 18:12:15 +02:00
|
|
|
$status = sprintf(t('File %s (%d bytes) '), $filename, $filesize);
|
2016-07-28 22:54:33 +02:00
|
|
|
if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
|
2017-05-09 18:12:15 +02:00
|
|
|
$status .= t('has an unknown file format. Nothing was imported.');
|
2016-07-28 22:54:33 +02:00
|
|
|
} else {
|
2017-05-09 18:12:15 +02:00
|
|
|
$status .= vsprintf(
|
2018-10-13 01:40:04 +02:00
|
|
|
t(
|
|
|
|
'was successfully processed in %d seconds: '
|
2018-12-04 00:13:42 +01:00
|
|
|
. '%d links imported, %d links overwritten, %d links skipped.'
|
2018-10-13 01:40:04 +02:00
|
|
|
),
|
2017-05-09 18:12:15 +02:00
|
|
|
[$duration, $importCount, $overwriteCount, $skipCount]
|
|
|
|
);
|
2016-07-28 22:54:33 +02:00
|
|
|
}
|
|
|
|
return $status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Imports Web bookmarks from an uploaded Netscape bookmark dump
|
|
|
|
*
|
2018-12-04 00:13:42 +01:00
|
|
|
* @param array $post Server $_POST parameters
|
|
|
|
* @param array $files Server $_FILES parameters
|
|
|
|
* @param LinkDB $linkDb Loaded LinkDB instance
|
|
|
|
* @param ConfigManager $conf instance
|
|
|
|
* @param History $history History instance
|
2016-07-28 22:54:33 +02:00
|
|
|
*
|
|
|
|
* @return string Summary of the bookmark import status
|
|
|
|
*/
|
2017-01-16 12:31:08 +01:00
|
|
|
public static function import($post, $files, $linkDb, $conf, $history)
|
2016-07-28 22:54:33 +02:00
|
|
|
{
|
2017-10-07 16:40:16 +02:00
|
|
|
$start = time();
|
2016-07-28 22:54:33 +02:00
|
|
|
$filename = $files['filetoupload']['name'];
|
|
|
|
$filesize = $files['filetoupload']['size'];
|
|
|
|
$data = file_get_contents($files['filetoupload']['tmp_name']);
|
|
|
|
|
2018-02-23 20:34:06 +01:00
|
|
|
if (preg_match('/<!DOCTYPE NETSCAPE-Bookmark-file-1>/i', $data) === 0) {
|
2016-07-28 22:54:33 +02:00
|
|
|
return self::importStatus($filename, $filesize);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Overwrite existing links?
|
2018-12-04 00:13:42 +01:00
|
|
|
$overwrite = !empty($post['overwrite']);
|
2016-07-28 22:54:33 +02:00
|
|
|
|
|
|
|
// Add tags to all imported links?
|
|
|
|
if (empty($post['default_tags'])) {
|
|
|
|
$defaultTags = array();
|
|
|
|
} else {
|
|
|
|
$defaultTags = preg_split(
|
|
|
|
'/[\s,]+/',
|
|
|
|
escape($post['default_tags'])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// links are imported as public by default
|
|
|
|
$defaultPrivacy = 0;
|
|
|
|
|
|
|
|
$parser = new NetscapeBookmarkParser(
|
2017-02-09 20:54:56 +01:00
|
|
|
true, // nested tag support
|
|
|
|
$defaultTags, // additional user-specified tags
|
|
|
|
strval(1 - $defaultPrivacy), // defaultPub = 1 - defaultPrivacy
|
|
|
|
$conf->get('resource.data_dir') // log path, will be overridden
|
|
|
|
);
|
|
|
|
$logger = new Logger(
|
|
|
|
$conf->get('resource.data_dir'),
|
2018-12-04 00:13:42 +01:00
|
|
|
!$conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG,
|
2017-02-09 20:54:56 +01:00
|
|
|
[
|
|
|
|
'prefix' => 'import.',
|
|
|
|
'extension' => 'log',
|
|
|
|
]
|
2016-07-28 22:54:33 +02:00
|
|
|
);
|
2017-02-09 20:54:56 +01:00
|
|
|
$parser->setLogger($logger);
|
2016-07-28 22:54:33 +02:00
|
|
|
$bookmarks = $parser->parseString($data);
|
|
|
|
|
|
|
|
$importCount = 0;
|
|
|
|
$overwriteCount = 0;
|
|
|
|
$skipCount = 0;
|
|
|
|
|
|
|
|
foreach ($bookmarks as $bkm) {
|
|
|
|
$private = $defaultPrivacy;
|
|
|
|
if (empty($post['privacy']) || $post['privacy'] == 'default') {
|
|
|
|
// use value from the imported file
|
|
|
|
$private = $bkm['pub'] == '1' ? 0 : 1;
|
2018-02-28 22:34:40 +01:00
|
|
|
} elseif ($post['privacy'] == 'private') {
|
2016-07-28 22:54:33 +02:00
|
|
|
// all imported links are private
|
|
|
|
$private = 1;
|
2018-02-28 22:34:40 +01:00
|
|
|
} elseif ($post['privacy'] == 'public') {
|
2016-07-28 22:54:33 +02:00
|
|
|
// all imported links are public
|
|
|
|
$private = 0;
|
2018-02-23 20:34:06 +01:00
|
|
|
}
|
2016-07-28 22:54:33 +02:00
|
|
|
|
|
|
|
$newLink = array(
|
|
|
|
'title' => $bkm['title'],
|
|
|
|
'url' => $bkm['uri'],
|
|
|
|
'description' => $bkm['note'],
|
|
|
|
'private' => $private,
|
|
|
|
'tags' => $bkm['tags']
|
|
|
|
);
|
|
|
|
|
|
|
|
$existingLink = $linkDb->getLinkFromUrl($bkm['uri']);
|
|
|
|
|
|
|
|
if ($existingLink !== false) {
|
|
|
|
if ($overwrite === false) {
|
|
|
|
// Do not overwrite an existing link
|
|
|
|
$skipCount++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Overwrite an existing link, keep its date
|
2016-11-28 16:16:44 +01:00
|
|
|
$newLink['id'] = $existingLink['id'];
|
|
|
|
$newLink['created'] = $existingLink['created'];
|
|
|
|
$newLink['updated'] = new DateTime();
|
2017-05-07 18:02:49 +02:00
|
|
|
$newLink['shorturl'] = $existingLink['shorturl'];
|
2016-11-28 16:16:44 +01:00
|
|
|
$linkDb[$existingLink['id']] = $newLink;
|
2016-07-28 22:54:33 +02:00
|
|
|
$importCount++;
|
|
|
|
$overwriteCount++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-28 16:16:44 +01:00
|
|
|
// Add a new link - @ used for UNIX timestamps
|
2018-12-04 00:13:42 +01:00
|
|
|
$newLinkDate = new DateTime('@' . strval($bkm['time']));
|
2016-11-28 16:16:44 +01:00
|
|
|
$newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
|
|
|
|
$newLink['created'] = $newLinkDate;
|
|
|
|
$newLink['id'] = $linkDb->getNextId();
|
2016-11-28 18:24:15 +01:00
|
|
|
$newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
|
2016-11-28 16:16:44 +01:00
|
|
|
$linkDb[$newLink['id']] = $newLink;
|
2016-07-28 22:54:33 +02:00
|
|
|
$importCount++;
|
|
|
|
}
|
|
|
|
|
2017-02-09 20:54:56 +01:00
|
|
|
$linkDb->save($conf->get('resource.page_cache'));
|
2017-10-07 16:40:16 +02:00
|
|
|
$history->importLinks();
|
|
|
|
|
|
|
|
$duration = time() - $start;
|
2016-07-28 22:54:33 +02:00
|
|
|
return self::importStatus(
|
|
|
|
$filename,
|
|
|
|
$filesize,
|
|
|
|
$importCount,
|
|
|
|
$overwriteCount,
|
2017-10-07 16:40:16 +02:00
|
|
|
$skipCount,
|
|
|
|
$duration
|
2016-07-28 22:54:33 +02:00
|
|
|
);
|
|
|
|
}
|
2016-04-10 17:34:07 +02:00
|
|
|
}
|