NetscapeBookmarkUtils.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. use Psr\Log\LogLevel;
  3. use Shaarli\Config\ConfigManager;
  4. use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
  5. use Katzgrau\KLogger\Logger;
  6. /**
  7. * Utilities to import and export bookmarks using the Netscape format
  8. * TODO: Not static, use a container.
  9. */
  10. class NetscapeBookmarkUtils
  11. {
  12. /**
  13. * Filters links and adds Netscape-formatted fields
  14. *
  15. * Added fields:
  16. * - timestamp link addition date, using the Unix epoch format
  17. * - taglist comma-separated tag list
  18. *
  19. * @param LinkDB $linkDb Link datastore
  20. * @param string $selection Which links to export: (all|private|public)
  21. * @param bool $prependNoteUrl Prepend note permalinks with the server's URL
  22. * @param string $indexUrl Absolute URL of the Shaarli index page
  23. *
  24. * @throws Exception Invalid export selection
  25. *
  26. * @return array The links to be exported, with additional fields
  27. */
  28. public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $indexUrl)
  29. {
  30. // see tpl/export.html for possible values
  31. if (! in_array($selection, array('all', 'public', 'private'))) {
  32. throw new Exception(t('Invalid export selection:') .' "'.$selection.'"');
  33. }
  34. $bookmarkLinks = array();
  35. foreach ($linkDb as $link) {
  36. if ($link['private'] != 0 && $selection == 'public') {
  37. continue;
  38. }
  39. if ($link['private'] == 0 && $selection == 'private') {
  40. continue;
  41. }
  42. $date = $link['created'];
  43. $link['timestamp'] = $date->getTimestamp();
  44. $link['taglist'] = str_replace(' ', ',', $link['tags']);
  45. if (startsWith($link['url'], '?') && $prependNoteUrl) {
  46. $link['url'] = $indexUrl . $link['url'];
  47. }
  48. $bookmarkLinks[] = $link;
  49. }
  50. return $bookmarkLinks;
  51. }
  52. /**
  53. * Generates an import status summary
  54. *
  55. * @param string $filename name of the file to import
  56. * @param int $filesize size of the file to import
  57. * @param int $importCount how many links were imported
  58. * @param int $overwriteCount how many links were overwritten
  59. * @param int $skipCount how many links were skipped
  60. * @param int $duration how many seconds did the import take
  61. *
  62. * @return string Summary of the bookmark import status
  63. */
  64. private static function importStatus(
  65. $filename,
  66. $filesize,
  67. $importCount = 0,
  68. $overwriteCount = 0,
  69. $skipCount = 0,
  70. $duration = 0
  71. ) {
  72. $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize);
  73. if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
  74. $status .= t('has an unknown file format. Nothing was imported.');
  75. } else {
  76. $status .= vsprintf(
  77. t(
  78. 'was successfully processed in %d seconds: '
  79. .'%d links imported, %d links overwritten, %d links skipped.'
  80. ),
  81. [$duration, $importCount, $overwriteCount, $skipCount]
  82. );
  83. }
  84. return $status;
  85. }
  86. /**
  87. * Imports Web bookmarks from an uploaded Netscape bookmark dump
  88. *
  89. * @param array $post Server $_POST parameters
  90. * @param array $files Server $_FILES parameters
  91. * @param LinkDB $linkDb Loaded LinkDB instance
  92. * @param ConfigManager $conf instance
  93. * @param History $history History instance
  94. *
  95. * @return string Summary of the bookmark import status
  96. */
  97. public static function import($post, $files, $linkDb, $conf, $history)
  98. {
  99. $start = time();
  100. $filename = $files['filetoupload']['name'];
  101. $filesize = $files['filetoupload']['size'];
  102. $data = file_get_contents($files['filetoupload']['tmp_name']);
  103. if (preg_match('/<!DOCTYPE NETSCAPE-Bookmark-file-1>/i', $data) === 0) {
  104. return self::importStatus($filename, $filesize);
  105. }
  106. // Overwrite existing links?
  107. $overwrite = ! empty($post['overwrite']);
  108. // Add tags to all imported links?
  109. if (empty($post['default_tags'])) {
  110. $defaultTags = array();
  111. } else {
  112. $defaultTags = preg_split(
  113. '/[\s,]+/',
  114. escape($post['default_tags'])
  115. );
  116. }
  117. // links are imported as public by default
  118. $defaultPrivacy = 0;
  119. $parser = new NetscapeBookmarkParser(
  120. true, // nested tag support
  121. $defaultTags, // additional user-specified tags
  122. strval(1 - $defaultPrivacy), // defaultPub = 1 - defaultPrivacy
  123. $conf->get('resource.data_dir') // log path, will be overridden
  124. );
  125. $logger = new Logger(
  126. $conf->get('resource.data_dir'),
  127. ! $conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG,
  128. [
  129. 'prefix' => 'import.',
  130. 'extension' => 'log',
  131. ]
  132. );
  133. $parser->setLogger($logger);
  134. $bookmarks = $parser->parseString($data);
  135. $importCount = 0;
  136. $overwriteCount = 0;
  137. $skipCount = 0;
  138. foreach ($bookmarks as $bkm) {
  139. $private = $defaultPrivacy;
  140. if (empty($post['privacy']) || $post['privacy'] == 'default') {
  141. // use value from the imported file
  142. $private = $bkm['pub'] == '1' ? 0 : 1;
  143. } elseif ($post['privacy'] == 'private') {
  144. // all imported links are private
  145. $private = 1;
  146. } elseif ($post['privacy'] == 'public') {
  147. // all imported links are public
  148. $private = 0;
  149. }
  150. $newLink = array(
  151. 'title' => $bkm['title'],
  152. 'url' => $bkm['uri'],
  153. 'description' => $bkm['note'],
  154. 'private' => $private,
  155. 'tags' => $bkm['tags']
  156. );
  157. $existingLink = $linkDb->getLinkFromUrl($bkm['uri']);
  158. if ($existingLink !== false) {
  159. if ($overwrite === false) {
  160. // Do not overwrite an existing link
  161. $skipCount++;
  162. continue;
  163. }
  164. // Overwrite an existing link, keep its date
  165. $newLink['id'] = $existingLink['id'];
  166. $newLink['created'] = $existingLink['created'];
  167. $newLink['updated'] = new DateTime();
  168. $newLink['shorturl'] = $existingLink['shorturl'];
  169. $linkDb[$existingLink['id']] = $newLink;
  170. $importCount++;
  171. $overwriteCount++;
  172. continue;
  173. }
  174. // Add a new link - @ used for UNIX timestamps
  175. $newLinkDate = new DateTime('@'.strval($bkm['time']));
  176. $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
  177. $newLink['created'] = $newLinkDate;
  178. $newLink['id'] = $linkDb->getNextId();
  179. $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
  180. $linkDb[$newLink['id']] = $newLink;
  181. $importCount++;
  182. }
  183. $linkDb->save($conf->get('resource.page_cache'));
  184. $history->importLinks();
  185. $duration = time() - $start;
  186. return self::importStatus(
  187. $filename,
  188. $filesize,
  189. $importCount,
  190. $overwriteCount,
  191. $skipCount,
  192. $duration
  193. );
  194. }
  195. }