mirror of
https://github.com/wallabag/wallabag.git
synced 2024-12-01 13:31:04 +00:00
Merge pull request #5794 from wallabag/2.5.0
Merge branch 2.5.0 in master
This commit is contained in:
commit
5809d7b072
40 changed files with 1160 additions and 355 deletions
|
@ -287,7 +287,7 @@ a.original:not(.waves-effect) {
|
||||||
flex-basis: 5em;
|
flex-basis: 5em;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
float: right;
|
float: right;
|
||||||
max-width: 6em;
|
max-width: 8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
|
|
|
@ -248,6 +248,11 @@ old_sound_rabbit_mq:
|
||||||
exchange_options:
|
exchange_options:
|
||||||
name: 'wallabag.import.pinboard'
|
name: 'wallabag.import.pinboard'
|
||||||
type: topic
|
type: topic
|
||||||
|
import_delicious:
|
||||||
|
connection: default
|
||||||
|
exchange_options:
|
||||||
|
name: 'wallabag.import.delicious'
|
||||||
|
type: topic
|
||||||
import_instapaper:
|
import_instapaper:
|
||||||
connection: default
|
connection: default
|
||||||
exchange_options:
|
exchange_options:
|
||||||
|
@ -315,6 +320,15 @@ old_sound_rabbit_mq:
|
||||||
name: 'wallabag.import.pinboard'
|
name: 'wallabag.import.pinboard'
|
||||||
callback: wallabag_import.consumer.amqp.pinboard
|
callback: wallabag_import.consumer.amqp.pinboard
|
||||||
qos_options: {prefetch_count: "%rabbitmq_prefetch_count%"}
|
qos_options: {prefetch_count: "%rabbitmq_prefetch_count%"}
|
||||||
|
import_delicious:
|
||||||
|
connection: default
|
||||||
|
exchange_options:
|
||||||
|
name: 'wallabag.import.delicious'
|
||||||
|
type: topic
|
||||||
|
queue_options:
|
||||||
|
name: 'wallabag.import.delicious'
|
||||||
|
callback: wallabag_import.consumer.amqp.delicious
|
||||||
|
qos_options: {prefetch_count: "%rabbitmq_prefetch_count%"}
|
||||||
import_wallabag_v1:
|
import_wallabag_v1:
|
||||||
connection: default
|
connection: default
|
||||||
exchange_options:
|
exchange_options:
|
||||||
|
|
|
@ -69,11 +69,11 @@ security:
|
||||||
- { path: ^/logout, roles: [IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_2FA_IN_PROGRESS] }
|
- { path: ^/logout, roles: [IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_2FA_IN_PROGRESS] }
|
||||||
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: /(unread|starred|archive|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: /(unread|starred|archive|annotated|all).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/locale, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/locale, role: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: /tags/(.*).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: /tags/(.*).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/feed, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/feed, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: /(unread|starred|archive).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } # For backwards compatibility
|
- { path: /(unread|starred|archive|annotated).xml$, roles: IS_AUTHENTICATED_ANONYMOUSLY } # For backwards compatibility
|
||||||
- { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
- { path: ^/share, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
- { path: ^/settings, roles: ROLE_SUPER_ADMIN }
|
- { path: ^/settings, roles: ROLE_SUPER_ADMIN }
|
||||||
- { path: ^/annotations, roles: ROLE_USER }
|
- { path: ^/annotations, roles: ROLE_USER }
|
||||||
|
|
|
@ -277,12 +277,26 @@ class EntryController extends Controller
|
||||||
return $this->showEntries('untagged', $request, $page);
|
return $this->showEntries('untagged', $request, $page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows entries with annotations for current user.
|
||||||
|
*
|
||||||
|
* @param int $page
|
||||||
|
*
|
||||||
|
* @Route("/annotated/list/{page}", name="annotated", defaults={"page" = "1"})
|
||||||
|
*
|
||||||
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
|
*/
|
||||||
|
public function showWithAnnotationsEntriesAction(Request $request, $page)
|
||||||
|
{
|
||||||
|
return $this->showEntries('annotated', $request, $page);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows random entry depending on the given type.
|
* Shows random entry depending on the given type.
|
||||||
*
|
*
|
||||||
* @param string $type
|
* @param string $type
|
||||||
*
|
*
|
||||||
* @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|all"})
|
* @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|annotated|all"})
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||||
*/
|
*/
|
||||||
|
@ -517,6 +531,20 @@ class EntryController extends Controller
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List the entries with the same domain as the current one.
|
||||||
|
*
|
||||||
|
* @param int $page
|
||||||
|
*
|
||||||
|
* @Route("/domain/{id}/{page}", requirements={"id" = ".+"}, defaults={"page" = 1}, name="same_domain")
|
||||||
|
*
|
||||||
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
|
*/
|
||||||
|
public function getSameDomainEntries(Request $request, $page = 1)
|
||||||
|
{
|
||||||
|
return $this->showEntries('same-domain', $request, $page);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global method to retrieve entries depending on the given type
|
* Global method to retrieve entries depending on the given type
|
||||||
* It returns the response to be send.
|
* It returns the response to be send.
|
||||||
|
@ -549,10 +577,16 @@ class EntryController extends Controller
|
||||||
$qb = $repository->getBuilderForArchiveByUser($this->getUser()->getId());
|
$qb = $repository->getBuilderForArchiveByUser($this->getUser()->getId());
|
||||||
$formOptions['filter_archived'] = true;
|
$formOptions['filter_archived'] = true;
|
||||||
break;
|
break;
|
||||||
|
case 'annotated':
|
||||||
|
$qb = $repository->getBuilderForAnnotationsByUser($this->getUser()->getId());
|
||||||
|
break;
|
||||||
case 'unread':
|
case 'unread':
|
||||||
$qb = $repository->getBuilderForUnreadByUser($this->getUser()->getId());
|
$qb = $repository->getBuilderForUnreadByUser($this->getUser()->getId());
|
||||||
$formOptions['filter_unread'] = true;
|
$formOptions['filter_unread'] = true;
|
||||||
break;
|
break;
|
||||||
|
case 'same-domain':
|
||||||
|
$qb = $repository->getBuilderForSameDomainByUser($this->getUser()->getId(), $request->get('id'));
|
||||||
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
$qb = $repository->getBuilderForAllByUser($this->getUser()->getId());
|
$qb = $repository->getBuilderForAllByUser($this->getUser()->getId());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -47,7 +47,7 @@ class ExportController extends Controller
|
||||||
*
|
*
|
||||||
* @Route("/export/{category}.{format}", name="export_entries", requirements={
|
* @Route("/export/{category}.{format}", name="export_entries", requirements={
|
||||||
* "format": "epub|mobi|pdf|json|xml|txt|csv",
|
* "format": "epub|mobi|pdf|json|xml|txt|csv",
|
||||||
* "category": "all|unread|starred|archive|tag_entries|untagged|search"
|
* "category": "all|unread|starred|archive|tag_entries|untagged|search|annotated|same_domain"
|
||||||
* })
|
* })
|
||||||
*
|
*
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
|
@ -80,6 +80,13 @@ class ExportController extends Controller
|
||||||
->getResult();
|
->getResult();
|
||||||
|
|
||||||
$title = 'Search ' . $searchTerm;
|
$title = 'Search ' . $searchTerm;
|
||||||
|
} elseif ('annotated' === $category) {
|
||||||
|
$entries = $repository->getBuilderForAnnotationsByUser(
|
||||||
|
$this->getUser()->getId()
|
||||||
|
)->getQuery()
|
||||||
|
->getResult();
|
||||||
|
|
||||||
|
$title = 'With annotations';
|
||||||
} else {
|
} else {
|
||||||
$entries = $repository
|
$entries = $repository
|
||||||
->$methodBuilder($this->getUser()->getId())
|
->$methodBuilder($this->getUser()->getId())
|
||||||
|
|
|
@ -93,7 +93,7 @@ class EntryFilterType extends AbstractType
|
||||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||||
$value = $values['value'];
|
$value = $values['value'];
|
||||||
if (\strlen($value) <= 2 || empty($value)) {
|
if (\strlen($value) <= 2 || empty($value)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
$expression = $filterQuery->getExpr()->like($field, $filterQuery->getExpr()->lower($filterQuery->getExpr()->literal('%' . $value . '%')));
|
$expression = $filterQuery->getExpr()->like($field, $filterQuery->getExpr()->lower($filterQuery->getExpr()->literal('%' . $value . '%')));
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ class EntryFilterType extends AbstractType
|
||||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||||
$value = $values['value'];
|
$value = $values['value'];
|
||||||
if (false === \array_key_exists($value, Response::$statusTexts)) {
|
if (false === \array_key_exists($value, Response::$statusTexts)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$paramName = sprintf('%s', str_replace('.', '_', $field));
|
$paramName = sprintf('%s', str_replace('.', '_', $field));
|
||||||
|
@ -129,7 +129,7 @@ class EntryFilterType extends AbstractType
|
||||||
'data' => $options['filter_unread'],
|
'data' => $options['filter_unread'],
|
||||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||||
if (false === $values['value']) {
|
if (false === $values['value']) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$expression = $filterQuery->getExpr()->eq('e.isArchived', 'false');
|
$expression = $filterQuery->getExpr()->eq('e.isArchived', 'false');
|
||||||
|
@ -137,10 +137,22 @@ class EntryFilterType extends AbstractType
|
||||||
return $filterQuery->createCondition($expression);
|
return $filterQuery->createCondition($expression);
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
->add('isAnnotated', CheckboxFilterType::class, [
|
||||||
|
'label' => 'entry.filters.annotated_label',
|
||||||
|
'data' => $options['filter_annotated'],
|
||||||
|
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||||
|
if (false === $values['value']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb = $filterQuery->getQueryBuilder();
|
||||||
|
$qb->innerJoin('e.annotations', 'a');
|
||||||
|
},
|
||||||
|
])
|
||||||
->add('previewPicture', CheckboxFilterType::class, [
|
->add('previewPicture', CheckboxFilterType::class, [
|
||||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||||
if (false === $values['value']) {
|
if (false === $values['value']) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$expression = $filterQuery->getExpr()->isNotNull($field);
|
$expression = $filterQuery->getExpr()->isNotNull($field);
|
||||||
|
@ -152,7 +164,7 @@ class EntryFilterType extends AbstractType
|
||||||
->add('isPublic', CheckboxFilterType::class, [
|
->add('isPublic', CheckboxFilterType::class, [
|
||||||
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
|
||||||
if (false === $values['value']) {
|
if (false === $values['value']) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// is_public isn't a real field
|
// is_public isn't a real field
|
||||||
|
@ -183,6 +195,7 @@ class EntryFilterType extends AbstractType
|
||||||
'filter_archived' => false,
|
'filter_archived' => false,
|
||||||
'filter_starred' => false,
|
'filter_starred' => false,
|
||||||
'filter_unread' => false,
|
'filter_unread' => false,
|
||||||
|
'filter_annotated' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,33 @@ class EntryRepository extends EntityRepository
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves entries with the same domain.
|
||||||
|
*
|
||||||
|
* @param int $userId
|
||||||
|
* @param int $entryId
|
||||||
|
*
|
||||||
|
* @return QueryBuilder
|
||||||
|
*/
|
||||||
|
public function getBuilderForSameDomainByUser($userId, $entryId)
|
||||||
|
{
|
||||||
|
$queryBuilder = $this->createQueryBuilder('e');
|
||||||
|
|
||||||
|
return $this
|
||||||
|
->getSortedQueryBuilderByUser($userId)
|
||||||
|
->andWhere('e.id <> :entryId')->setParameter('entryId', $entryId)
|
||||||
|
->andWhere(
|
||||||
|
$queryBuilder->expr()->in(
|
||||||
|
'e.domainName',
|
||||||
|
$this
|
||||||
|
->createQueryBuilder('e2')
|
||||||
|
->select('e2.domainName')
|
||||||
|
->where('e2.id = :entryId')->setParameter('entryId', $entryId)
|
||||||
|
->getDQL()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves read entries for a user.
|
* Retrieves read entries for a user.
|
||||||
*
|
*
|
||||||
|
@ -115,6 +142,21 @@ class EntryRepository extends EntityRepository
|
||||||
return $this->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
|
return $this->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve entries with annotations for a user.
|
||||||
|
*
|
||||||
|
* @param int $userId
|
||||||
|
*
|
||||||
|
* @return QueryBuilder
|
||||||
|
*/
|
||||||
|
public function getBuilderForAnnotationsByUser($userId)
|
||||||
|
{
|
||||||
|
return $this
|
||||||
|
->getSortedQueryBuilderByUser($userId)
|
||||||
|
->innerJoin('e.annotations', 'a')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve untagged entries for a user.
|
* Retrieve untagged entries for a user.
|
||||||
*
|
*
|
||||||
|
@ -552,6 +594,10 @@ class EntryRepository extends EntityRepository
|
||||||
$qb->leftJoin('e.tags', 't');
|
$qb->leftJoin('e.tags', 't');
|
||||||
$qb->andWhere('t.id is null');
|
$qb->andWhere('t.id is null');
|
||||||
break;
|
break;
|
||||||
|
case 'annotated':
|
||||||
|
$qb->leftJoin('e.annotations', 'a');
|
||||||
|
$qb->andWhere('a.id is not null');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = $qb->getQuery()->getArrayResult();
|
$ids = $qb->getQuery()->getArrayResult();
|
||||||
|
|
|
@ -19,6 +19,7 @@ menu:
|
||||||
starred: Starred
|
starred: Starred
|
||||||
archive: Archive
|
archive: Archive
|
||||||
all_articles: All entries
|
all_articles: All entries
|
||||||
|
with_annotations: With annotations
|
||||||
config: Config
|
config: Config
|
||||||
tags: Tags
|
tags: Tags
|
||||||
internal_settings: Internal Settings
|
internal_settings: Internal Settings
|
||||||
|
@ -220,10 +221,12 @@ entry:
|
||||||
starred: Starred entries
|
starred: Starred entries
|
||||||
archived: Archived entries
|
archived: Archived entries
|
||||||
filtered: Filtered entries
|
filtered: Filtered entries
|
||||||
|
with_annotations: Entries with annotations
|
||||||
filtered_tags: 'Filtered by tags:'
|
filtered_tags: 'Filtered by tags:'
|
||||||
filtered_search: 'Filtered by search:'
|
filtered_search: 'Filtered by search:'
|
||||||
untagged: Untagged entries
|
untagged: Untagged entries
|
||||||
all: All entries
|
all: All entries
|
||||||
|
same_domain: Same domain
|
||||||
list:
|
list:
|
||||||
number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
|
number_on_the_page: '{0} There are no entries.|{1} There is one entry.|]1,Inf[ There are %count% entries.'
|
||||||
reading_time: estimated reading time
|
reading_time: estimated reading time
|
||||||
|
@ -237,6 +240,7 @@ entry:
|
||||||
toogle_as_star: Toggle starred
|
toogle_as_star: Toggle starred
|
||||||
delete: Delete
|
delete: Delete
|
||||||
export_title: Export
|
export_title: Export
|
||||||
|
show_same_domain: Show articles with the same domain
|
||||||
assign_search_tag: Assign this search as a tag to each result
|
assign_search_tag: Assign this search as a tag to each result
|
||||||
filters:
|
filters:
|
||||||
title: Filters
|
title: Filters
|
||||||
|
@ -244,6 +248,7 @@ entry:
|
||||||
archived_label: Archived
|
archived_label: Archived
|
||||||
starred_label: Starred
|
starred_label: Starred
|
||||||
unread_label: Unread
|
unread_label: Unread
|
||||||
|
annotated_label: Annotated
|
||||||
preview_picture_label: Has a preview picture
|
preview_picture_label: Has a preview picture
|
||||||
preview_picture_help: Preview picture
|
preview_picture_help: Preview picture
|
||||||
is_public_label: Has a public link
|
is_public_label: Has a public link
|
||||||
|
@ -481,12 +486,12 @@ import:
|
||||||
description: Pocket import isn't configured.
|
description: Pocket import isn't configured.
|
||||||
admin_message: You need to define %keyurls%a pocket_consumer_key%keyurle%.
|
admin_message: You need to define %keyurls%a pocket_consumer_key%keyurle%.
|
||||||
user_message: Your server admin needs to define an API Key for Pocket.
|
user_message: Your server admin needs to define an API Key for Pocket.
|
||||||
authorize_message: You can import your data from your Pocket account. You just have to click on the below button and authorize the application to connect to getpocket.com.
|
authorize_message: You can import your data from your Pocket account. You just have to click on the button below and authorize the application to connect to getpocket.com.
|
||||||
connect_to_pocket: Connect to Pocket and import data
|
connect_to_pocket: Connect to Pocket and import data
|
||||||
wallabag_v1:
|
wallabag_v1:
|
||||||
page_title: Import > Wallabag v1
|
page_title: Import > Wallabag v1
|
||||||
description: This importer will import all your wallabag v1 articles. On your config page, click on "JSON export" in the "Export your wallabag data" section. You will have a "wallabag-export-1-xxxx-xx-xx.json" file.
|
description: This importer will import all your wallabag v1 articles. On your config page, click on "JSON export" in the "Export your wallabag data" section. You will have a "wallabag-export-1-xxxx-xx-xx.json" file.
|
||||||
how_to: Please select your wallabag export and click on the below button to upload and import it.
|
how_to: Please select your wallabag export and click on the button below to upload and import it.
|
||||||
wallabag_v2:
|
wallabag_v2:
|
||||||
page_title: Import > Wallabag v2
|
page_title: Import > Wallabag v2
|
||||||
description: This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.
|
description: This importer will import all your wallabag v2 articles. Go to All articles, then, on the export sidebar, click on "JSON". You will have a "All articles.json" file.
|
||||||
|
@ -496,7 +501,7 @@ import:
|
||||||
readability:
|
readability:
|
||||||
page_title: Import > Readability
|
page_title: Import > Readability
|
||||||
description: This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).
|
description: This importer will import all your Readability articles. On the tools (https://www.readability.com/tools/) page, click on "Export your data" in the "Data Export" section. You will received an email to download a json (which does not end with .json in fact).
|
||||||
how_to: Please select your Readability export and click on the below button to upload and import it.
|
how_to: Please select your Readability export and click on the button below to upload and import it.
|
||||||
worker:
|
worker:
|
||||||
enabled: 'Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:'
|
enabled: 'Import is made asynchronously. Once the import task is started, an external worker will handle jobs one at a time. The current service is:'
|
||||||
download_images_warning: You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors.
|
download_images_warning: You enabled downloading images for your articles. Combined with classic import it can take ages to proceed (or maybe failed). We <strong>strongly recommend</strong> to enable asynchronous import to avoid errors.
|
||||||
|
@ -511,11 +516,15 @@ import:
|
||||||
instapaper:
|
instapaper:
|
||||||
page_title: Import > Instapaper
|
page_title: Import > Instapaper
|
||||||
description: This importer will import all your Instapaper articles. On the settings (https://www.instapaper.com/user) page, click on "Download .CSV file" in the "Export" section. A CSV file will be downloaded (like "instapaper-export.csv").
|
description: This importer will import all your Instapaper articles. On the settings (https://www.instapaper.com/user) page, click on "Download .CSV file" in the "Export" section. A CSV file will be downloaded (like "instapaper-export.csv").
|
||||||
how_to: Please select your Instapaper export and click on the below button to upload and import it.
|
how_to: Please select your Instapaper export and click on the button below to upload and import it.
|
||||||
pinboard:
|
pinboard:
|
||||||
page_title: Import > Pinboard
|
page_title: Import > Pinboard
|
||||||
description: This importer will import all your Pinboard articles. On the backup (https://pinboard.in/settings/backup) page, click on "JSON" in the "Bookmarks" section. A JSON file will be downloaded (like "pinboard_export").
|
description: This importer will import all your Pinboard articles. On the backup (https://pinboard.in/settings/backup) page, click on "JSON" in the "Bookmarks" section. A JSON file will be downloaded (like "pinboard_export").
|
||||||
how_to: Please select your Pinboard export and click on the below button to upload and import it.
|
how_to: Please select your Pinboard export and click on the button below to upload and import it.
|
||||||
|
delicious:
|
||||||
|
page_title: Import > del.icio.us
|
||||||
|
description: This importer will import all your Delicious bookmarks. Since 2021, you can export again your data from it using the export page (https://del.icio.us/export). Choose the "JSON" format and download it (like "delicious_export.2021.02.06_21.10.json").
|
||||||
|
how_to: Please select your Delicious export and click on the button below to upload and import it.
|
||||||
developer:
|
developer:
|
||||||
page_title: API clients management
|
page_title: API clients management
|
||||||
welcome_message: Welcome to the wallabag API
|
welcome_message: Welcome to the wallabag API
|
||||||
|
|
|
@ -32,6 +32,7 @@ menu:
|
||||||
site_credentials: اعتبارنامههای وبگاه
|
site_credentials: اعتبارنامههای وبگاه
|
||||||
users_management: مدیریت کاربران
|
users_management: مدیریت کاربران
|
||||||
developer: مدیریت کارخواههای API
|
developer: مدیریت کارخواههای API
|
||||||
|
quickstart: "Quickstart"
|
||||||
top:
|
top:
|
||||||
add_new_entry: افزودن مقالهٔ تازه
|
add_new_entry: افزودن مقالهٔ تازه
|
||||||
search: جستجو
|
search: جستجو
|
||||||
|
|
|
@ -19,6 +19,7 @@ menu:
|
||||||
starred: Favoris
|
starred: Favoris
|
||||||
archive: Lus
|
archive: Lus
|
||||||
all_articles: Tous les articles
|
all_articles: Tous les articles
|
||||||
|
with_annotations: Avec annotations
|
||||||
config: Configuration
|
config: Configuration
|
||||||
tags: Étiquettes
|
tags: Étiquettes
|
||||||
internal_settings: Configuration interne
|
internal_settings: Configuration interne
|
||||||
|
@ -220,6 +221,7 @@ entry:
|
||||||
starred: Articles favoris
|
starred: Articles favoris
|
||||||
archived: Articles lus
|
archived: Articles lus
|
||||||
filtered: Articles filtrés
|
filtered: Articles filtrés
|
||||||
|
with_annotations: Articles avec annotations
|
||||||
filtered_tags: 'Articles filtrés par étiquettes :'
|
filtered_tags: 'Articles filtrés par étiquettes :'
|
||||||
filtered_search: 'Articles filtrés par recherche :'
|
filtered_search: 'Articles filtrés par recherche :'
|
||||||
untagged: Article sans étiquette
|
untagged: Article sans étiquette
|
||||||
|
@ -243,6 +245,7 @@ entry:
|
||||||
archived_label: Lus
|
archived_label: Lus
|
||||||
starred_label: Favoris
|
starred_label: Favoris
|
||||||
unread_label: Non lus
|
unread_label: Non lus
|
||||||
|
annotated_label: Annotés
|
||||||
preview_picture_label: A une photo
|
preview_picture_label: A une photo
|
||||||
preview_picture_help: Photo
|
preview_picture_help: Photo
|
||||||
is_public_label: A un lien public
|
is_public_label: A un lien public
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
{{ 'entry.page_titles.filtered_tags'|trans }} {{ filter }}
|
{{ 'entry.page_titles.filtered_tags'|trans }} {{ filter }}
|
||||||
{% elseif currentRoute == 'untagged' %}
|
{% elseif currentRoute == 'untagged' %}
|
||||||
{{ 'entry.page_titles.untagged'|trans }}
|
{{ 'entry.page_titles.untagged'|trans }}
|
||||||
|
{% elseif currentRoute == 'same_domain' %}
|
||||||
|
{{ 'entry.page_titles.same_domain'|trans }}
|
||||||
|
{% elseif currentRoute == 'annotated' %}
|
||||||
|
{{ 'entry.page_titles.with_annotations'|trans }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ 'entry.page_titles.unread'|trans }}
|
{{ 'entry.page_titles.unread'|trans }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="tools right">
|
<ul class="tools right">
|
||||||
|
<li>
|
||||||
|
<a title="{{ 'entry.list.show_same_domain'|trans }}" class="tool grey-text" href="{{ path('same_domain', { 'id': entry.id }) }}" data-action="same_domain" data-entry-id="{{ entry.id }}"><i class="material-icons">language</i></a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}" data-action="archived" data-entry-id="{{ entry.id }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}" data-action="archived" data-entry-id="{{ entry.id }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
{% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry, 'withMetadata': true, 'subClass': 'metadata'} only %}
|
{% include "@WallabagCore/themes/material/Entry/Card/_content.html.twig" with {'entry': entry, 'withMetadata': true, 'subClass': 'metadata'} only %}
|
||||||
<ul class="tools-list hide-on-small-only">
|
<ul class="tools-list hide-on-small-only">
|
||||||
<li>
|
<li>
|
||||||
|
<a title="{{ 'entry.list.show_same_domain'|trans }}" class="tool grey-text" href="{{ path('same_domain', { 'id': entry.id }) }}"><i class="material-icons">language</i></a>
|
||||||
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
||||||
<a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
|
<a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
|
||||||
<a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a>
|
<a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a>
|
||||||
|
|
|
@ -134,6 +134,11 @@
|
||||||
{{ form_label(form.isUnread) }}
|
{{ form_label(form.isUnread) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field col s12 with-checkbox">
|
||||||
|
{{ form_widget(form.isAnnotated) }}
|
||||||
|
{{ form_label(form.isAnnotated) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<label>{{ 'entry.filters.preview_picture_help'|trans }}</label>
|
<label>{{ 'entry.filters.preview_picture_help'|trans }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
{% set activeRoute = null %}
|
{% set activeRoute = null %}
|
||||||
{% if currentRoute == 'all' or currentRouteFromQueryParams == 'all' %}
|
{% if currentRoute == 'all' or currentRouteFromQueryParams == 'all' %}
|
||||||
{% set activeRoute = 'all' %}
|
{% set activeRoute = 'all' %}
|
||||||
|
{% elseif currentRoute == 'annotated' or currentRouteFromQueryParams == 'annotated' %}
|
||||||
|
{% set activeRoute = 'annotated' %}
|
||||||
{% elseif currentRoute == 'archive' or currentRouteFromQueryParams == 'archive' %}
|
{% elseif currentRoute == 'archive' or currentRouteFromQueryParams == 'archive' %}
|
||||||
{% set activeRoute = 'archive' %}
|
{% set activeRoute = 'archive' %}
|
||||||
{% elseif currentRoute == 'starred' or currentRouteFromQueryParams == 'starred' %}
|
{% elseif currentRoute == 'starred' or currentRouteFromQueryParams == 'starred' %}
|
||||||
|
@ -59,6 +61,9 @@
|
||||||
<li class="bold {% if activeRoute == 'archive' %}active{% endif %}">
|
<li class="bold {% if activeRoute == 'archive' %}active{% endif %}">
|
||||||
<a class="waves-effect" href="{{ path('archive') }}">{{ 'menu.left.archive'|trans }} <span class="numberItems grey-text">{{ count_entries('archive') }}</span></a>
|
<a class="waves-effect" href="{{ path('archive') }}">{{ 'menu.left.archive'|trans }} <span class="numberItems grey-text">{{ count_entries('archive') }}</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="bold {% if activeRoute == 'annotated' %}active{% endif %}">
|
||||||
|
<a class="waves-effect" href="{{ path('annotated') }}">{{ 'menu.left.with_annotations'|trans }} <span class="numberItems grey-text">{{ count_entries('annotated') }}</span></a>
|
||||||
|
</li>
|
||||||
<li class="bold {% if activeRoute == 'all' %}active{% endif %}">
|
<li class="bold {% if activeRoute == 'all' %}active{% endif %}">
|
||||||
<a class="waves-effect" href="{{ path('all') }}">{{ 'menu.left.all_articles'|trans }} <span class="numberItems grey-text">{{ count_entries('all') }}</span></a>
|
<a class="waves-effect" href="{{ path('all') }}">{{ 'menu.left.all_articles'|trans }} <span class="numberItems grey-text">{{ count_entries('all') }}</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -95,6 +95,9 @@ class WallabagExtension extends AbstractExtension implements GlobalsInterface
|
||||||
case 'unread':
|
case 'unread':
|
||||||
$qb = $this->entryRepository->getBuilderForUnreadByUser($user->getId());
|
$qb = $this->entryRepository->getBuilderForUnreadByUser($user->getId());
|
||||||
break;
|
break;
|
||||||
|
case 'annotated':
|
||||||
|
$qb = $this->entryRepository->getBuilderForAnnotationsByUser($user->getId());
|
||||||
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
$qb = $this->entryRepository->getBuilderForAllByUser($user->getId());
|
$qb = $this->entryRepository->getBuilderForAllByUser($user->getId());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -19,7 +19,7 @@ class ImportCommand extends ContainerAwareCommand
|
||||||
->setDescription('Import entries from a JSON export')
|
->setDescription('Import entries from a JSON export')
|
||||||
->addArgument('username', InputArgument::REQUIRED, 'User to populate')
|
->addArgument('username', InputArgument::REQUIRED, 'User to populate')
|
||||||
->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
|
->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
|
||||||
->addOption('importer', null, InputOption::VALUE_OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, readability, firefox or chrome', 'v1')
|
->addOption('importer', null, InputOption::VALUE_OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, delicious, readability, firefox or chrome', 'v1')
|
||||||
->addOption('markAsRead', null, InputOption::VALUE_OPTIONAL, 'Mark all entries as read', false)
|
->addOption('markAsRead', null, InputOption::VALUE_OPTIONAL, 'Mark all entries as read', false)
|
||||||
->addOption('useUserId', null, InputOption::VALUE_NONE, 'Use user id instead of username to find account')
|
->addOption('useUserId', null, InputOption::VALUE_NONE, 'Use user id instead of username to find account')
|
||||||
->addOption('disableContentUpdate', null, InputOption::VALUE_NONE, 'Disable fetching updated content from URL')
|
->addOption('disableContentUpdate', null, InputOption::VALUE_NONE, 'Disable fetching updated content from URL')
|
||||||
|
@ -77,6 +77,9 @@ class ImportCommand extends ContainerAwareCommand
|
||||||
case 'pinboard':
|
case 'pinboard':
|
||||||
$import = $this->getContainer()->get('wallabag_import.pinboard.import');
|
$import = $this->getContainer()->get('wallabag_import.pinboard.import');
|
||||||
break;
|
break;
|
||||||
|
case 'delicious':
|
||||||
|
$import = $this->getContainer()->get('wallabag_import.delicious.import');
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$import = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
|
$import = $this->getContainer()->get('wallabag_import.wallabag_v1.import');
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ class RedisWorkerCommand extends ContainerAwareCommand
|
||||||
$this
|
$this
|
||||||
->setName('wallabag:import:redis-worker')
|
->setName('wallabag:import:redis-worker')
|
||||||
->setDescription('Launch Redis worker')
|
->setDescription('Launch Redis worker')
|
||||||
->addArgument('serviceName', InputArgument::REQUIRED, 'Service to use: wallabag_v1, wallabag_v2, pocket, readability, pinboard, firefox, chrome or instapaper')
|
->addArgument('serviceName', InputArgument::REQUIRED, 'Service to use: wallabag_v1, wallabag_v2, pocket, readability, pinboard, delicious, firefox, chrome or instapaper')
|
||||||
->addOption('maxIterations', '', InputOption::VALUE_OPTIONAL, 'Number of iterations before stoping', false)
|
->addOption('maxIterations', '', InputOption::VALUE_OPTIONAL, 'Number of iterations before stoping', false)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
77
src/Wallabag/ImportBundle/Controller/DeliciousController.php
Normal file
77
src/Wallabag/ImportBundle/Controller/DeliciousController.php
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Wallabag\ImportBundle\Controller;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Wallabag\ImportBundle\Form\Type\UploadImportType;
|
||||||
|
|
||||||
|
class DeliciousController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/delicious", name="import_delicious")
|
||||||
|
*/
|
||||||
|
public function indexAction(Request $request)
|
||||||
|
{
|
||||||
|
$form = $this->createForm(UploadImportType::class);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
$delicious = $this->get('wallabag_import.delicious.import');
|
||||||
|
$delicious->setUser($this->getUser());
|
||||||
|
|
||||||
|
if ($this->get('craue_config')->get('import_with_rabbitmq')) {
|
||||||
|
$delicious->setProducer($this->get('old_sound_rabbit_mq.import_delicious_producer'));
|
||||||
|
} elseif ($this->get('craue_config')->get('import_with_redis')) {
|
||||||
|
$delicious->setProducer($this->get('wallabag_import.producer.redis.delicious'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$file = $form->get('file')->getData();
|
||||||
|
$markAsRead = $form->get('mark_as_read')->getData();
|
||||||
|
$name = 'delicious_' . $this->getUser()->getId() . '.json';
|
||||||
|
|
||||||
|
if (null !== $file && \in_array($file->getClientMimeType(), $this->getParameter('wallabag_import.allow_mimetypes'), true) && $file->move($this->getParameter('wallabag_import.resource_dir'), $name)) {
|
||||||
|
$res = $delicious
|
||||||
|
->setFilepath($this->getParameter('wallabag_import.resource_dir') . '/' . $name)
|
||||||
|
->setMarkAsRead($markAsRead)
|
||||||
|
->import();
|
||||||
|
|
||||||
|
$message = 'flashes.import.notice.failed';
|
||||||
|
|
||||||
|
if (true === $res) {
|
||||||
|
$summary = $delicious->getSummary();
|
||||||
|
$message = $this->get('translator')->trans('flashes.import.notice.summary', [
|
||||||
|
'%imported%' => $summary['imported'],
|
||||||
|
'%skipped%' => $summary['skipped'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (0 < $summary['queued']) {
|
||||||
|
$message = $this->get('translator')->trans('flashes.import.notice.summary_with_queue', [
|
||||||
|
'%queued%' => $summary['queued'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink($this->getParameter('wallabag_import.resource_dir') . '/' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->get('session')->getFlashBag()->add(
|
||||||
|
'notice',
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->redirect($this->generateUrl('homepage'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->get('session')->getFlashBag()->add(
|
||||||
|
'notice',
|
||||||
|
'flashes.import.notice.failed_on_file'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('WallabagImportBundle:Delicious:index.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'import' => $delicious,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ class ImportController extends Controller
|
||||||
+ $this->getTotalMessageInRabbitQueue('chrome')
|
+ $this->getTotalMessageInRabbitQueue('chrome')
|
||||||
+ $this->getTotalMessageInRabbitQueue('instapaper')
|
+ $this->getTotalMessageInRabbitQueue('instapaper')
|
||||||
+ $this->getTotalMessageInRabbitQueue('pinboard')
|
+ $this->getTotalMessageInRabbitQueue('pinboard')
|
||||||
|
+ $this->getTotalMessageInRabbitQueue('delicious')
|
||||||
+ $this->getTotalMessageInRabbitQueue('elcurator')
|
+ $this->getTotalMessageInRabbitQueue('elcurator')
|
||||||
;
|
;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@ -60,6 +61,7 @@ class ImportController extends Controller
|
||||||
+ $redis->llen('wallabag.import.chrome')
|
+ $redis->llen('wallabag.import.chrome')
|
||||||
+ $redis->llen('wallabag.import.instapaper')
|
+ $redis->llen('wallabag.import.instapaper')
|
||||||
+ $redis->llen('wallabag.import.pinboard')
|
+ $redis->llen('wallabag.import.pinboard')
|
||||||
|
+ $redis->llen('wallabag.import.delicious')
|
||||||
+ $redis->llen('wallabag.import.elcurator')
|
+ $redis->llen('wallabag.import.elcurator')
|
||||||
;
|
;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
151
src/Wallabag/ImportBundle/Import/DeliciousImport.php
Normal file
151
src/Wallabag/ImportBundle/Import/DeliciousImport.php
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Wallabag\ImportBundle\Import;
|
||||||
|
|
||||||
|
use Wallabag\CoreBundle\Entity\Entry;
|
||||||
|
|
||||||
|
class DeliciousImport extends AbstractImport
|
||||||
|
{
|
||||||
|
private $filepath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'Delicious';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getUrl()
|
||||||
|
{
|
||||||
|
return 'import_delicious';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getDescription()
|
||||||
|
{
|
||||||
|
return 'import.delicious.description';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set file path to the json file.
|
||||||
|
*
|
||||||
|
* @param string $filepath
|
||||||
|
*/
|
||||||
|
public function setFilepath($filepath)
|
||||||
|
{
|
||||||
|
$this->filepath = $filepath;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function import()
|
||||||
|
{
|
||||||
|
if (!$this->user) {
|
||||||
|
$this->logger->error('DeliciousImport: user is not defined');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
|
||||||
|
$this->logger->error('DeliciousImport: unable to read file', ['filepath' => $this->filepath]);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents($this->filepath), true);
|
||||||
|
|
||||||
|
if (empty($data)) {
|
||||||
|
$this->logger->error('DeliciousImport: no entries in imported file');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->producer) {
|
||||||
|
$this->parseEntriesForProducer($data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->parseEntries($data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validateEntry(array $importedEntry)
|
||||||
|
{
|
||||||
|
if (empty($importedEntry['url'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function parseEntry(array $importedEntry)
|
||||||
|
{
|
||||||
|
$existingEntry = $this->em
|
||||||
|
->getRepository('WallabagCoreBundle:Entry')
|
||||||
|
->findByUrlAndUserId($importedEntry['url'], $this->user->getId());
|
||||||
|
|
||||||
|
if (false !== $existingEntry) {
|
||||||
|
++$this->skippedEntries;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'title' => $importedEntry['title'],
|
||||||
|
'url' => $importedEntry['url'],
|
||||||
|
'is_archived' => $this->markAsRead,
|
||||||
|
'is_starred' => false,
|
||||||
|
'created_at' => $importedEntry['created'],
|
||||||
|
'tags' => $importedEntry['tags'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$entry = new Entry($this->user);
|
||||||
|
$entry->setUrl($data['url']);
|
||||||
|
$entry->setTitle($data['title']);
|
||||||
|
|
||||||
|
// update entry with content (in case fetching failed, the given entry will be return)
|
||||||
|
$this->fetchContent($entry, $data['url'], $data);
|
||||||
|
|
||||||
|
if (!empty($data['tags'])) {
|
||||||
|
$this->tagsAssigner->assignTagsToEntry(
|
||||||
|
$entry,
|
||||||
|
$data['tags'],
|
||||||
|
$this->em->getUnitOfWork()->getScheduledEntityInsertions()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry->updateArchived($data['is_archived']);
|
||||||
|
$entry->setStarred($data['is_starred']);
|
||||||
|
$entry->setCreatedAt(\DateTime::createFromFormat('U', $data['created_at']));
|
||||||
|
|
||||||
|
$this->em->persist($entry);
|
||||||
|
++$this->importedEntries;
|
||||||
|
|
||||||
|
return $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function setEntryAsRead(array $importedEntry)
|
||||||
|
{
|
||||||
|
return $importedEntry;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,14 @@ services:
|
||||||
- "@wallabag_import.pinboard.import"
|
- "@wallabag_import.pinboard.import"
|
||||||
- "@event_dispatcher"
|
- "@event_dispatcher"
|
||||||
- "@logger"
|
- "@logger"
|
||||||
|
wallabag_import.consumer.amqp.delicious:
|
||||||
|
class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
|
||||||
|
arguments:
|
||||||
|
- "@doctrine.orm.entity_manager"
|
||||||
|
- "@wallabag_user.user_repository"
|
||||||
|
- "@wallabag_import.delicious.import"
|
||||||
|
- "@event_dispatcher"
|
||||||
|
- "@logger"
|
||||||
wallabag_import.consumer.amqp.wallabag_v1:
|
wallabag_import.consumer.amqp.wallabag_v1:
|
||||||
class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
|
class: Wallabag\ImportBundle\Consumer\AMQPEntryConsumer
|
||||||
arguments:
|
arguments:
|
||||||
|
|
|
@ -63,6 +63,27 @@ services:
|
||||||
- "@event_dispatcher"
|
- "@event_dispatcher"
|
||||||
- "@logger"
|
- "@logger"
|
||||||
|
|
||||||
|
# delicious
|
||||||
|
wallabag_import.queue.redis.delicious:
|
||||||
|
class: Simpleue\Queue\RedisQueue
|
||||||
|
arguments:
|
||||||
|
- "@wallabag_core.redis.client"
|
||||||
|
- "wallabag.import.delicious"
|
||||||
|
|
||||||
|
wallabag_import.producer.redis.delicious:
|
||||||
|
class: Wallabag\ImportBundle\Redis\Producer
|
||||||
|
arguments:
|
||||||
|
- "@wallabag_import.queue.redis.delicious"
|
||||||
|
|
||||||
|
wallabag_import.consumer.redis.delicious:
|
||||||
|
class: Wallabag\ImportBundle\Consumer\RedisEntryConsumer
|
||||||
|
arguments:
|
||||||
|
- "@doctrine.orm.entity_manager"
|
||||||
|
- "@wallabag_user.user_repository"
|
||||||
|
- "@wallabag_import.delicious.import"
|
||||||
|
- "@event_dispatcher"
|
||||||
|
- "@logger"
|
||||||
|
|
||||||
# pocket
|
# pocket
|
||||||
wallabag_import.queue.redis.pocket:
|
wallabag_import.queue.redis.pocket:
|
||||||
class: Simpleue\Queue\RedisQueue
|
class: Simpleue\Queue\RedisQueue
|
||||||
|
|
|
@ -96,6 +96,18 @@ services:
|
||||||
tags:
|
tags:
|
||||||
- { name: wallabag_import.import, alias: pinboard }
|
- { name: wallabag_import.import, alias: pinboard }
|
||||||
|
|
||||||
|
wallabag_import.delicious.import:
|
||||||
|
class: Wallabag\ImportBundle\Import\DeliciousImport
|
||||||
|
arguments:
|
||||||
|
- "@doctrine.orm.entity_manager"
|
||||||
|
- "@wallabag_core.content_proxy"
|
||||||
|
- "@wallabag_core.tags_assigner"
|
||||||
|
- "@event_dispatcher"
|
||||||
|
calls:
|
||||||
|
- [ setLogger, [ "@logger" ]]
|
||||||
|
tags:
|
||||||
|
- { name: wallabag_import.import, alias: delicious }
|
||||||
|
|
||||||
wallabag_import.firefox.import:
|
wallabag_import.firefox.import:
|
||||||
class: Wallabag\ImportBundle\Import\FirefoxImport
|
class: Wallabag\ImportBundle\Import\FirefoxImport
|
||||||
arguments:
|
arguments:
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
{% extends "WallabagCoreBundle::layout.html.twig" %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'import.delicious.page_title'|trans }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card-panel settings">
|
||||||
|
{% include 'WallabagImportBundle:Import:_information.html.twig' %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<blockquote>{{ import.description|trans }}</blockquote>
|
||||||
|
<p>{{ 'import.delicious.how_to'|trans }}</p>
|
||||||
|
|
||||||
|
<div class="col s12">
|
||||||
|
{{ form_start(form, {'method': 'POST'}) }}
|
||||||
|
{{ form_errors(form) }}
|
||||||
|
<div class="row">
|
||||||
|
<div class="file-field input-field col s12">
|
||||||
|
{{ form_errors(form.file) }}
|
||||||
|
<div class="btn">
|
||||||
|
<span>{{ form.file.vars.label|trans }}</span>
|
||||||
|
{{ form_widget(form.file) }}
|
||||||
|
</div>
|
||||||
|
<div class="file-path-wrapper">
|
||||||
|
<input class="file-path validate" type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6 with-checkbox">
|
||||||
|
<h6>{{ 'import.form.mark_as_read_title'|trans }}</h6>
|
||||||
|
{{ form_widget(form.mark_as_read) }}
|
||||||
|
{{ form_label(form.mark_as_read) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ form_widget(form.save, { 'attr': {'class': 'btn waves-effect waves-light'} }) }}
|
||||||
|
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Tests\Wallabag\CoreBundle\Controller;
|
namespace Tests\Wallabag\CoreBundle\Controller;
|
||||||
|
|
||||||
use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
|
use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
|
||||||
|
use Wallabag\AnnotationBundle\Entity\Annotation;
|
||||||
use Wallabag\CoreBundle\Entity\Config;
|
use Wallabag\CoreBundle\Entity\Config;
|
||||||
use Wallabag\CoreBundle\Entity\Entry;
|
use Wallabag\CoreBundle\Entity\Entry;
|
||||||
use Wallabag\CoreBundle\Entity\SiteCredential;
|
use Wallabag\CoreBundle\Entity\SiteCredential;
|
||||||
|
@ -448,6 +449,16 @@ class EntryControllerTest extends WallabagCoreTestCase
|
||||||
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testWithAnnotations()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/annotated/list');
|
||||||
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
|
$this->assertCount(2, $crawler->filter('ol.entries > li'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testRangeException()
|
public function testRangeException()
|
||||||
{
|
{
|
||||||
$this->logInAs('admin');
|
$this->logInAs('admin');
|
||||||
|
@ -915,6 +926,44 @@ class EntryControllerTest extends WallabagCoreTestCase
|
||||||
$this->assertCount(0, $crawler->filter($this->entryDataTestAttribute));
|
$this->assertCount(0, $crawler->filter($this->entryDataTestAttribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFilterOnAnnotatedStatus()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/all/list');
|
||||||
|
|
||||||
|
$form = $crawler->filter('button[id=submit-filter]')->form();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'entry_filter[isAnnotated]' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
$crawler = $client->submit($form, $data);
|
||||||
|
|
||||||
|
$this->assertCount(2, $crawler->filter('ol.entries > li'));
|
||||||
|
|
||||||
|
$entry = new Entry($this->getLoggedInUser());
|
||||||
|
$entry->setUrl($this->url);
|
||||||
|
|
||||||
|
$em = $this->getClient()->getContainer()->get('doctrine.orm.entity_manager');
|
||||||
|
$user = $em
|
||||||
|
->getRepository('WallabagUserBundle:User')
|
||||||
|
->findOneByUserName('admin');
|
||||||
|
|
||||||
|
$annotation = new Annotation($user);
|
||||||
|
$annotation->setEntry($entry);
|
||||||
|
$annotation->setText('This is my annotation /o/');
|
||||||
|
$annotation->setQuote('content');
|
||||||
|
|
||||||
|
$this->getEntityManager()->persist($entry);
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
|
||||||
|
$crawler = $client->submit($form, $data);
|
||||||
|
|
||||||
|
$this->assertCount(3, $crawler->filter('ol.entries > li'));
|
||||||
|
}
|
||||||
|
|
||||||
public function testPaginationWithFilter()
|
public function testPaginationWithFilter()
|
||||||
{
|
{
|
||||||
$this->logInAs('admin');
|
$this->logInAs('admin');
|
||||||
|
@ -1627,6 +1676,10 @@ class EntryControllerTest extends WallabagCoreTestCase
|
||||||
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||||
$this->assertStringContainsString('/view/', $client->getResponse()->getTargetUrl(), 'Untagged random');
|
$this->assertStringContainsString('/view/', $client->getResponse()->getTargetUrl(), 'Untagged random');
|
||||||
|
|
||||||
|
$client->request('GET', '/annotated/random');
|
||||||
|
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||||
|
$this->assertStringContainsString('/view/', $client->getResponse()->getTargetUrl(), 'With annotations random');
|
||||||
|
|
||||||
$client->request('GET', '/all/random');
|
$client->request('GET', '/all/random');
|
||||||
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||||
$this->assertStringContainsString('/view/', $client->getResponse()->getTargetUrl(), 'All random');
|
$this->assertStringContainsString('/view/', $client->getResponse()->getTargetUrl(), 'All random');
|
||||||
|
@ -1708,4 +1761,14 @@ class EntryControllerTest extends WallabagCoreTestCase
|
||||||
$client->request('GET', '/delete/' . $entry2->getId());
|
$client->request('GET', '/delete/' . $entry2->getId());
|
||||||
$this->assertSame(404, $client->getResponse()->getStatusCode());
|
$this->assertSame(404, $client->getResponse()->getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetSameDomainEntries()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/domain/1');
|
||||||
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
|
$this->assertCount(4, $crawler->filter('ol.entries > li'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Wallabag\ImportBundle\Controller;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
use Tests\Wallabag\CoreBundle\WallabagCoreTestCase;
|
||||||
|
|
||||||
|
class DeliciousControllerTest extends WallabagCoreTestCase
|
||||||
|
{
|
||||||
|
public function testImportDelicious()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/import/delicious');
|
||||||
|
|
||||||
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
|
$this->assertSame(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
|
||||||
|
$this->assertSame(1, $crawler->filter('input[type=file]')->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportDeliciousWithRabbitEnabled()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1);
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/import/delicious');
|
||||||
|
|
||||||
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
|
$this->assertSame(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
|
||||||
|
$this->assertSame(1, $crawler->filter('input[type=file]')->count());
|
||||||
|
|
||||||
|
$client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportDeliciousBadFile()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/import/delicious');
|
||||||
|
$form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'upload_import_file[file]' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
$client->submit($form, $data);
|
||||||
|
|
||||||
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportDeliciousWithRedisEnabled()
|
||||||
|
{
|
||||||
|
$this->checkRedis();
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
$client->getContainer()->get('craue_config')->set('import_with_redis', 1);
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/import/delicious');
|
||||||
|
|
||||||
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
|
$this->assertSame(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count());
|
||||||
|
$this->assertSame(1, $crawler->filter('input[type=file]')->count());
|
||||||
|
|
||||||
|
$form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
|
||||||
|
|
||||||
|
$file = new UploadedFile(__DIR__ . '/../fixtures/delicious_export.2021.02.06_21.10.json', 'delicious.json');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'upload_import_file[file]' => $file,
|
||||||
|
];
|
||||||
|
|
||||||
|
$client->submit($form, $data);
|
||||||
|
|
||||||
|
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||||
|
|
||||||
|
$crawler = $client->followRedirect();
|
||||||
|
|
||||||
|
$this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
|
||||||
|
$this->assertStringContainsString('flashes.import.notice.summary', $body[0]);
|
||||||
|
|
||||||
|
$this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.delicious'));
|
||||||
|
|
||||||
|
$client->getContainer()->get('craue_config')->set('import_with_redis', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportDeliciousWithFile()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/import/delicious');
|
||||||
|
$form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
|
||||||
|
|
||||||
|
$file = new UploadedFile(__DIR__ . '/../fixtures/delicious_export.2021.02.06_21.10.json', 'delicious.json');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'upload_import_file[file]' => $file,
|
||||||
|
];
|
||||||
|
|
||||||
|
$client->submit($form, $data);
|
||||||
|
|
||||||
|
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||||
|
|
||||||
|
$crawler = $client->followRedirect();
|
||||||
|
|
||||||
|
$content = $client->getContainer()
|
||||||
|
->get('doctrine.orm.entity_manager')
|
||||||
|
->getRepository('WallabagCoreBundle:Entry')
|
||||||
|
->findByUrlAndUserId(
|
||||||
|
'https://feross.org/spoofmac/',
|
||||||
|
$this->getLoggedInUserId()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
|
||||||
|
$this->assertStringContainsString('flashes.import.notice.summary', $body[0]);
|
||||||
|
|
||||||
|
$this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content);
|
||||||
|
|
||||||
|
$tags = $content->getTagsLabel();
|
||||||
|
$this->assertContains('osx', $tags, 'It includes the "osx" tag');
|
||||||
|
$this->assertGreaterThanOrEqual(4, \count($tags));
|
||||||
|
|
||||||
|
$this->assertInstanceOf(\DateTime::class, $content->getCreatedAt());
|
||||||
|
$this->assertSame('2013-01-17', $content->getCreatedAt()->format('Y-m-d'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportDeliciousWithFileAndMarkAllAsRead()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/import/delicious');
|
||||||
|
$form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
|
||||||
|
|
||||||
|
$file = new UploadedFile(__DIR__ . '/../fixtures/delicious_export.2021.02.06_21.10.json', 'delicious-read.json');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'upload_import_file[file]' => $file,
|
||||||
|
'upload_import_file[mark_as_read]' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
$client->submit($form, $data);
|
||||||
|
|
||||||
|
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||||
|
|
||||||
|
$crawler = $client->followRedirect();
|
||||||
|
|
||||||
|
$content1 = $client->getContainer()
|
||||||
|
->get('doctrine.orm.entity_manager')
|
||||||
|
->getRepository('WallabagCoreBundle:Entry')
|
||||||
|
->findByUrlAndUserId(
|
||||||
|
'https://stackoverflow.com/review/',
|
||||||
|
$this->getLoggedInUserId()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content1);
|
||||||
|
|
||||||
|
$content2 = $client->getContainer()
|
||||||
|
->get('doctrine.orm.entity_manager')
|
||||||
|
->getRepository('WallabagCoreBundle:Entry')
|
||||||
|
->findByUrlAndUserId(
|
||||||
|
'https://addyosmani.com/basket.js/',
|
||||||
|
$this->getLoggedInUserId()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertInstanceOf('Wallabag\CoreBundle\Entity\Entry', $content2);
|
||||||
|
|
||||||
|
$this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
|
||||||
|
$this->assertStringContainsString('flashes.import.notice.summary', $body[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportDeliciousWithEmptyFile()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getClient();
|
||||||
|
|
||||||
|
$crawler = $client->request('GET', '/import/delicious');
|
||||||
|
$form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form();
|
||||||
|
|
||||||
|
$file = new UploadedFile(__DIR__ . '/../fixtures/test.txt', 'test.txt');
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'upload_import_file[file]' => $file,
|
||||||
|
];
|
||||||
|
|
||||||
|
$client->submit($form, $data);
|
||||||
|
|
||||||
|
$this->assertSame(302, $client->getResponse()->getStatusCode());
|
||||||
|
|
||||||
|
$crawler = $client->followRedirect();
|
||||||
|
|
||||||
|
$this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text']));
|
||||||
|
$this->assertStringContainsString('flashes.import.notice.failed', $body[0]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,6 @@ class ImportControllerTest extends WallabagCoreTestCase
|
||||||
$crawler = $client->request('GET', '/import/');
|
$crawler = $client->request('GET', '/import/');
|
||||||
|
|
||||||
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
$this->assertSame(9, $crawler->filter('blockquote')->count());
|
$this->assertSame(10, $crawler->filter('blockquote')->count());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "basket.js - a simple script loader that caches scripts with localStorage",
|
||||||
|
"tags": [
|
||||||
|
"basket",
|
||||||
|
"javascript",
|
||||||
|
"loader",
|
||||||
|
"localStorage"
|
||||||
|
],
|
||||||
|
"url": "https://addyosmani.com/basket.js/",
|
||||||
|
"description": "\"A simple (proof-of-concept) script loader that caches scripts with localStorage\"",
|
||||||
|
"created": "1358531607",
|
||||||
|
"others": 9,
|
||||||
|
"owner": "maciej",
|
||||||
|
"private": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Review - Stack Overflow",
|
||||||
|
"tags": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"url": "https://stackoverflow.com/review/",
|
||||||
|
"description": "",
|
||||||
|
"created": "1358457437",
|
||||||
|
"others": 84,
|
||||||
|
"owner": "maciej",
|
||||||
|
"private": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Announcing SpoofMAC - Spoof your MAC address in Mac OS X",
|
||||||
|
"tags": [
|
||||||
|
"MAC_address",
|
||||||
|
"osx",
|
||||||
|
"mac",
|
||||||
|
"spoof"
|
||||||
|
],
|
||||||
|
"url": "https://feross.org/spoofmac/",
|
||||||
|
"description": "",
|
||||||
|
"created": "1358425796",
|
||||||
|
"others": 6,
|
||||||
|
"owner": "maciej",
|
||||||
|
"private": "0"
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,3 +1,35 @@
|
||||||
[{"href":"https:\/\/developers.google.com\/web\/updates\/2016\/07\/infinite-scroller","description":"Complexities of an Infinite Scroller","extended":"TL;DR: Re-use your DOM elements and remove the ones that are far away from the viewport. Use placeholders to account for delayed data","meta":"21ff61c6f648901168f9e6119f53df7d","hash":"e69b65724cca1c585b446d4c47865d76","time":"2016-10-31T15:57:56Z","shared":"yes","toread":"no","tags":"infinite dom performance scroll"},
|
[
|
||||||
{"href":"https:\/\/ma.ttias.be\/varnish-explained\/","description":"Varnish (explained) for PHP developers","extended":"A few months ago, I gave a presentation at LaraconEU in Amsterdam titled \"Varnish for PHP developers\". The generic title of that presentation is actually Varnish Explained and this is a write-up of that presentation, the video and the slides.","meta":"d32ad9fac2ed29da4aec12c562e9afb1","hash":"21dd6bdda8ad62666a2c9e79f6e80f98","time":"2016-10-26T06:43:03Z","shared":"yes","toread":"no","tags":"varnish PHP"},
|
{
|
||||||
{"href":"https:\/\/ilia.ws\/files\/nginx_torontophpug.pdf","description":"Nginx Tricks for PHP Developers","extended":"","meta":"9adbb5c4ca6760e335b920800d88c70a","hash":"0189bb08f8bd0122c6544bed4624c546","time":"2016-10-05T07:11:27Z","shared":"yes","toread":"no","tags":"nginx PHP best_practice"}]
|
"href": "https://developers.google.com/web/updates/2016/07/infinite-scroller",
|
||||||
|
"description": "Complexities of an Infinite Scroller",
|
||||||
|
"extended": "TL;DR: Re-use your DOM elements and remove the ones that are far away from the viewport. Use placeholders to account for delayed data",
|
||||||
|
"meta": "21ff61c6f648901168f9e6119f53df7d",
|
||||||
|
"hash": "e69b65724cca1c585b446d4c47865d76",
|
||||||
|
"time": "2016-10-31T15:57:56Z",
|
||||||
|
"shared": "yes",
|
||||||
|
"toread": "no",
|
||||||
|
"tags": "infinite dom performance scroll"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://ma.ttias.be/varnish-explained/",
|
||||||
|
"description": "Varnish (explained) for PHP developers",
|
||||||
|
"extended": "A few months ago, I gave a presentation at LaraconEU in Amsterdam titled \"Varnish for PHP developers\". The generic title of that presentation is actually Varnish Explained and this is a write-up of that presentation, the video and the slides.",
|
||||||
|
"meta": "d32ad9fac2ed29da4aec12c562e9afb1",
|
||||||
|
"hash": "21dd6bdda8ad62666a2c9e79f6e80f98",
|
||||||
|
"time": "2016-10-26T06:43:03Z",
|
||||||
|
"shared": "yes",
|
||||||
|
"toread": "no",
|
||||||
|
"tags": "varnish PHP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://ilia.ws/files/nginx_torontophpug.pdf",
|
||||||
|
"description": "Nginx Tricks for PHP Developers",
|
||||||
|
"extended": "",
|
||||||
|
"meta": "9adbb5c4ca6760e335b920800d88c70a",
|
||||||
|
"hash": "0189bb08f8bd0122c6544bed4624c546",
|
||||||
|
"time": "2016-10-05T07:11:27Z",
|
||||||
|
"shared": "yes",
|
||||||
|
"toread": "no",
|
||||||
|
"tags": "nginx PHP best_practice"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue