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;
|
2019-01-12 23:55:38 +01:00
|
|
|
use Katzgrau\KLogger\Logger;
|
2020-06-17 19:08:02 +02:00
|
|
|
use Psr\Http\Message\UploadedFileInterface;
|
2017-02-09 20:54:56 +01:00
|
|
|
use Psr\Log\LogLevel;
|
2019-05-25 15:52:27 +02:00
|
|
|
use Shaarli\Bookmark\Bookmark;
|
|
|
|
use Shaarli\Bookmark\BookmarkServiceInterface;
|
2017-03-10 18:49:53 +01:00
|
|
|
use Shaarli\Config\ConfigManager;
|
2019-05-25 15:52:27 +02:00
|
|
|
use Shaarli\Formatter\BookmarkFormatter;
|
2018-12-02 23:24:58 +01:00
|
|
|
use Shaarli\History;
|
2017-02-09 20:54:56 +01:00
|
|
|
use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
|
|
|
|
|
2016-04-10 17:34:07 +02:00
|
|
|
/**
|
|
|
|
* Utilities to import and export bookmarks using the Netscape format
|
|
|
|
*/
|
|
|
|
class NetscapeBookmarkUtils
|
|
|
|
{
|
2020-06-17 15:55:31 +02:00
|
|
|
/** @var BookmarkServiceInterface */
|
|
|
|
protected $bookmarkService;
|
|
|
|
|
|
|
|
/** @var ConfigManager */
|
|
|
|
protected $conf;
|
|
|
|
|
|
|
|
/** @var History */
|
|
|
|
protected $history;
|
|
|
|
|
|
|
|
public function __construct(BookmarkServiceInterface $bookmarkService, ConfigManager $conf, History $history)
|
|
|
|
{
|
|
|
|
$this->bookmarkService = $bookmarkService;
|
|
|
|
$this->conf = $conf;
|
|
|
|
$this->history = $history;
|
|
|
|
}
|
2016-04-10 17:34:07 +02:00
|
|
|
|
|
|
|
/**
|
2019-05-25 15:52:27 +02:00
|
|
|
* Filters bookmarks and adds Netscape-formatted fields
|
2016-04-10 17:34:07 +02:00
|
|
|
*
|
|
|
|
* Added fields:
|
|
|
|
* - timestamp link addition date, using the Unix epoch format
|
|
|
|
* - taglist comma-separated tag list
|
|
|
|
*
|
2019-05-25 15:52:27 +02:00
|
|
|
* @param BookmarkFormatter $formatter instance
|
|
|
|
* @param string $selection Which bookmarks 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
|
|
|
*
|
2019-05-25 15:52:27 +02:00
|
|
|
* @return array The bookmarks to be exported, with additional fields
|
2016-04-10 17:34:07 +02:00
|
|
|
*
|
2020-06-17 15:55:31 +02:00
|
|
|
* @throws Exception Invalid export selection
|
2016-04-10 17:34:07 +02:00
|
|
|
*/
|
2020-06-17 15:55:31 +02:00
|
|
|
public function filterAndFormat(
|
2019-05-25 15:52:27 +02:00
|
|
|
$formatter,
|
|
|
|
$selection,
|
|
|
|
$prependNoteUrl,
|
|
|
|
$indexUrl
|
|
|
|
) {
|
2016-04-10 17:34:07 +02:00
|
|
|
// see tpl/export.html for possible values
|
2020-09-22 20:25:47 +02:00
|
|
|
if (!in_array($selection, ['all', 'public', 'private'])) {
|
2018-12-04 00:13:42 +01:00
|
|
|
throw new Exception(t('Invalid export selection:') . ' "' . $selection . '"');
|
2016-04-10 17:34:07 +02:00
|
|
|
}
|
|
|
|
|
2020-09-22 20:25:47 +02:00
|
|
|
$bookmarkLinks = [];
|
2020-06-17 15:55:31 +02:00
|
|
|
foreach ($this->bookmarkService->search([], $selection) as $bookmark) {
|
2019-05-25 15:52:27 +02:00
|
|
|
$link = $formatter->format($bookmark);
|
|
|
|
$link['taglist'] = implode(',', $bookmark->getTags());
|
|
|
|
if ($bookmark->isNote() && $prependNoteUrl) {
|
2020-07-28 20:46:11 +02:00
|
|
|
$link['url'] = rtrim($indexUrl, '/') . '/' . ltrim($link['url'], '/');
|
2016-05-05 19:22:06 +02:00
|
|
|
}
|
|
|
|
|
2016-04-10 17:34:07 +02:00
|
|
|
$bookmarkLinks[] = $link;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $bookmarkLinks;
|
|
|
|
}
|
2016-07-28 22:54:33 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Imports Web bookmarks from an uploaded Netscape bookmark dump
|
|
|
|
*
|
2020-06-17 19:08:02 +02:00
|
|
|
* @param array $post Server $_POST parameters
|
|
|
|
* @param UploadedFileInterface $file File in PSR-7 object format
|
2016-07-28 22:54:33 +02:00
|
|
|
*
|
|
|
|
* @return string Summary of the bookmark import status
|
|
|
|
*/
|
2020-06-17 19:08:02 +02:00
|
|
|
public function import($post, UploadedFileInterface $file)
|
2016-07-28 22:54:33 +02:00
|
|
|
{
|
2017-10-07 16:40:16 +02:00
|
|
|
$start = time();
|
2020-06-17 19:08:02 +02:00
|
|
|
$filename = $file->getClientFilename();
|
|
|
|
$filesize = $file->getSize();
|
|
|
|
$data = (string) $file->getStream();
|
2016-07-28 22:54:33 +02:00
|
|
|
|
2018-02-23 20:34:06 +01:00
|
|
|
if (preg_match('/<!DOCTYPE NETSCAPE-Bookmark-file-1>/i', $data) === 0) {
|
2020-06-17 19:08:02 +02:00
|
|
|
return $this->importStatus($filename, $filesize);
|
2016-07-28 22:54:33 +02:00
|
|
|
}
|
|
|
|
|
2019-05-25 15:52:27 +02:00
|
|
|
// Overwrite existing bookmarks?
|
2018-12-04 00:13:42 +01:00
|
|
|
$overwrite = !empty($post['overwrite']);
|
2016-07-28 22:54:33 +02:00
|
|
|
|
2019-05-25 15:52:27 +02:00
|
|
|
// Add tags to all imported bookmarks?
|
2016-07-28 22:54:33 +02:00
|
|
|
if (empty($post['default_tags'])) {
|
2020-10-22 16:21:03 +02:00
|
|
|
$defaultTags = [];
|
2016-07-28 22:54:33 +02:00
|
|
|
} else {
|
2020-10-22 16:21:03 +02:00
|
|
|
$defaultTags = tags_str2array(
|
|
|
|
escape($post['default_tags']),
|
|
|
|
$this->conf->get('general.tags_separator', ' ')
|
2016-07-28 22:54:33 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-25 15:52:27 +02:00
|
|
|
// bookmarks are imported as public by default
|
2016-07-28 22:54:33 +02:00
|
|
|
$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
|
2020-06-17 15:55:31 +02:00
|
|
|
$this->conf->get('resource.data_dir') // log path, will be overridden
|
2017-02-09 20:54:56 +01:00
|
|
|
);
|
|
|
|
$logger = new Logger(
|
2020-06-17 15:55:31 +02:00
|
|
|
$this->conf->get('resource.data_dir'),
|
|
|
|
!$this->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') {
|
2019-05-25 15:52:27 +02:00
|
|
|
// all imported bookmarks are private
|
2016-07-28 22:54:33 +02:00
|
|
|
$private = 1;
|
2018-02-28 22:34:40 +01:00
|
|
|
} elseif ($post['privacy'] == 'public') {
|
2019-05-25 15:52:27 +02:00
|
|
|
// all imported bookmarks are public
|
2016-07-28 22:54:33 +02:00
|
|
|
$private = 0;
|
2018-02-23 20:34:06 +01:00
|
|
|
}
|
2016-07-28 22:54:33 +02:00
|
|
|
|
2020-06-17 15:55:31 +02:00
|
|
|
$link = $this->bookmarkService->findByUrl($bkm['uri']);
|
2019-05-25 15:52:27 +02:00
|
|
|
$existingLink = $link !== null;
|
|
|
|
if (! $existingLink) {
|
|
|
|
$link = new Bookmark();
|
|
|
|
}
|
2016-07-28 22:54:33 +02:00
|
|
|
|
|
|
|
if ($existingLink !== false) {
|
|
|
|
if ($overwrite === false) {
|
|
|
|
// Do not overwrite an existing link
|
|
|
|
$skipCount++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-25 15:52:27 +02:00
|
|
|
$link->setUpdated(new DateTime());
|
2016-07-28 22:54:33 +02:00
|
|
|
$overwriteCount++;
|
2019-05-25 15:52:27 +02:00
|
|
|
} else {
|
|
|
|
$newLinkDate = new DateTime('@' . strval($bkm['time']));
|
|
|
|
$newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
|
|
|
|
$link->setCreated($newLinkDate);
|
2016-07-28 22:54:33 +02:00
|
|
|
}
|
|
|
|
|
2019-05-25 15:52:27 +02:00
|
|
|
$link->setTitle($bkm['title']);
|
2020-06-17 15:55:31 +02:00
|
|
|
$link->setUrl($bkm['uri'], $this->conf->get('security.allowed_protocols'));
|
2019-05-25 15:52:27 +02:00
|
|
|
$link->setDescription($bkm['note']);
|
|
|
|
$link->setPrivate($private);
|
2020-10-22 16:21:03 +02:00
|
|
|
$link->setTags($bkm['tags']);
|
2019-05-25 15:52:27 +02:00
|
|
|
|
2020-06-17 15:55:31 +02:00
|
|
|
$this->bookmarkService->addOrSet($link, false);
|
2016-07-28 22:54:33 +02:00
|
|
|
$importCount++;
|
|
|
|
}
|
|
|
|
|
2020-06-17 15:55:31 +02:00
|
|
|
$this->bookmarkService->save();
|
|
|
|
$this->history->importLinks();
|
2017-10-07 16:40:16 +02:00
|
|
|
|
|
|
|
$duration = time() - $start;
|
2020-06-17 15:55:31 +02:00
|
|
|
|
|
|
|
return $this->importStatus(
|
2016-07-28 22:54:33 +02:00
|
|
|
$filename,
|
|
|
|
$filesize,
|
|
|
|
$importCount,
|
|
|
|
$overwriteCount,
|
2017-10-07 16:40:16 +02:00
|
|
|
$skipCount,
|
|
|
|
$duration
|
2016-07-28 22:54:33 +02:00
|
|
|
);
|
|
|
|
}
|
2020-06-17 15:55:31 +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 bookmarks were imported
|
|
|
|
* @param int $overwriteCount how many bookmarks were overwritten
|
|
|
|
* @param int $skipCount how many bookmarks were skipped
|
|
|
|
* @param int $duration how many seconds did the import take
|
|
|
|
*
|
|
|
|
* @return string Summary of the bookmark import status
|
|
|
|
*/
|
|
|
|
protected function importStatus(
|
|
|
|
$filename,
|
|
|
|
$filesize,
|
|
|
|
$importCount = 0,
|
|
|
|
$overwriteCount = 0,
|
|
|
|
$skipCount = 0,
|
|
|
|
$duration = 0
|
|
|
|
) {
|
|
|
|
$status = sprintf(t('File %s (%d bytes) '), $filename, $filesize);
|
|
|
|
if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
|
|
|
|
$status .= t('has an unknown file format. Nothing was imported.');
|
|
|
|
} else {
|
|
|
|
$status .= vsprintf(
|
|
|
|
t(
|
|
|
|
'was successfully processed in %d seconds: '
|
|
|
|
. '%d bookmarks imported, %d bookmarks overwritten, %d bookmarks skipped.'
|
|
|
|
),
|
|
|
|
[$duration, $importCount, $overwriteCount, $skipCount]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return $status;
|
|
|
|
}
|
2016-04-10 17:34:07 +02:00
|
|
|
}
|