1:- module(bc_data_entry, [
    2    bc_entry_actions/3,       % +Actor, +Id, -Actions
    3    bc_entry_action/4,        % +Actor, +Id, +Action, -Result
    4    bc_entry_save/3,          % +Actor, +Entry, -Id
    5    bc_entry_update/2,        % +Actor, +Entry
    6    bc_entry_remove/2,        % +Actor, +Id
    7    bc_entry_remove_trash/2,  % +Actor, +Id
    8    bc_entry_restore/2,       % +Actor, +Id
    9    bc_entry_list/3,          % +Actor, +Type, -List
   10    bc_trash_list/2,          % +Actor, -List
   11    bc_purge_trash/1,         % +Actor
   12    bc_entry/3,               % +Actor, +Id, -Entry
   13    bc_entry_info/3           % +Actor, +Id, -Entry
   14]).

Handles entry data */

   18:- use_module(library(debug)).   19:- use_module(library(sort_dict)).   20:- use_module(library(docstore)).   21:- use_module(library(md/md_parse)).   22
   23:- use_module(bc_access).   24:- use_module(bc_search).   25:- use_module(bc_entry).   26:- use_module(bc_files).   27:- use_module(bc_user).   28:- use_module(bc_action).
 bc_entry_actions(+Actor, +Id, -Actions) is det
List of available actions for the actor.
   34bc_entry_actions(Actor, Id, Actions):-
   35    bc_available_actions(Actor, Id, Actions).
 bc_entry_action(+Actor, +Id, +Action, -Result) is det
Executes the given action on the entry.
   41bc_entry_action(Actor, Id, Action, Result):-
   42    can_execute(Actor, Action, Id),
   43    bc_execute(Actor, Action, Id, Result).
   44
   45can_execute(Actor, Action, Id):-
   46    bc_execute_access_id(Actor, Action, Id), !.
   47
   48can_execute(_, _, _):-
   49    throw(error(no_access)).
 bc_entry_save(+Actor, +Entry, -Id) is det
Saves and formats the new entry.
   55bc_entry_save(Actor, Entry, Id):-
   56    can_create(Actor, Entry),
   57    entry_format(Entry, Formatted),
   58    ds_insert(Formatted, Id),
   59    bc_index(Id),
   60    debug(bc_data_entry, 'saved entry ~p', [Id]).
   61
   62can_create(Actor, Entry):-
   63    bc_valid_slug(Entry.slug),
   64    bc_slug_unique(Entry.slug),
   65    bc_user_exists(Entry.author),
   66    create_access(Actor, Entry).
   67
   68create_access(Actor, Entry):-
   69    bc_create_access_type(Actor, Entry.type), !.
   70
   71create_access(_, _):-
   72    throw(error(no_access)).
 bc_entry_update(+Actor, +Entry) is det
Updates the given entry. Reformats HTML.
   78bc_entry_update(Actor, Entry):-
   79    ds_id(Entry, Id),
   80    can_update(Actor, Entry),
   81    bc_entry_slug(Id, OldSlug),
   82    entry_format(Entry, OldSlug, Formatted),
   83    ds_update(Formatted),
   84    bc_index(Id),
   85    rename_directory(OldSlug, Entry.slug),
   86    debug(bc_data_entry, 'updated entry ~p', [Id]).
   87
   88can_update(Actor, Entry):-
   89    ds_id(Entry, Id),
   90    bc_entry_exists(Id),
   91    bc_valid_slug(Entry.slug),
   92    bc_slug_unique(Entry.slug, Id),
   93    bc_user_exists(Entry.author),
   94    update_access(Actor, Entry).
   95
   96update_access(Actor, Entry):-
   97    ds_id(Entry, Id),
   98    update_type_access(Actor, Entry),
   99    update_author_access(Actor, Entry),
  100    bc_update_access_id(Actor, Id),
  101    bc_entry_published(Id, Published),
  102    (   Entry.published = Published
  103    ->  true
  104    ;   bc_publish_access_id(Actor, Id)), !.
  105
  106update_access(_, _):-
  107    throw(error(no_access)).
  108
  109% Checks if the entry
  110% type can be updated.
  111
  112update_type_access(Actor, _):-
  113    Actor.type = admin, !.
  114
  115update_type_access(_, Entry):-
  116    ds_id(Entry, Id),
  117    bc_entry_type(Id, Entry.type), !.
  118
  119update_type_access(_, _):-
  120    throw(error(no_access)).
  121
  122% Checks if the entry
  123% author can be updated.
  124
  125update_author_access(Actor, _):-
  126    Actor.type = admin, !.
  127
  128update_author_access(_, Entry):-
  129    ds_id(Entry, Id),
  130    bc_entry_author(Id, Entry.author), !.
  131
  132update_author_access(_, _):-
  133    throw(error(no_access)).
  134
  135% Renames entry files directory
  136% when the entry slug changes.
  137
  138rename_directory(Slug, Slug):- !.
  139
  140rename_directory(Old, New):-
  141    atomic_list_concat([public, '/', Old], From),
  142    atomic_list_concat([public, '/', New], To),
  143    (   exists_directory(From)
  144    ->  rename_file(From, To)
  145    ;   true).
  146
  147% Formats entry HTML contents based on
  148% the entries content type.
  149
  150entry_format(EntryIn, OldSlug, EntryOut):-
  151    links_rewrite(EntryIn.content,
  152        EntryIn.slug, OldSlug, Content),
  153    Rewritten = EntryIn.put(content, Content),
  154    entry_format(Rewritten, EntryOut).
  155
  156% Replaces slug in links in the content.
  157
  158links_rewrite(ContentIn, NewSlug, OldSlug, ContentOut):-
  159    atomic_list_concat(['/', OldSlug, '/'], OldLink),
  160    atomic_list_concat(['/', NewSlug, '/'], NewLink),
  161    atomic_list_concat(Tokens, OldLink, ContentIn),
  162    atomic_list_concat(Tokens, NewLink, ContentAtom),
  163    atom_string(ContentAtom, ContentOut).
  164
  165entry_format(EntryIn, EntryOut):-
  166    Content = EntryIn.content,
  167    ContentType = EntryIn.content_type,
  168    (   ContentType = markdown
  169    ->  md_html_string(Content, Html)
  170    ;   Html = Content),
  171    put_dict(_{ html: Html }, EntryIn, EntryOut).
 bc_entry_remove(+Actor, +Id) is det
Removes the given entry and its comments.
  177bc_entry_remove(Actor, Id):-
  178    can_remove(Actor, Id),
  179    ds_move(entry, Id, trash),
  180    debug(bc_data_entry, 'moved to trash: ~p', [Id]).
 bc_entry_remove_trash(+Actor, +Id) is det
Removes entry from trash.
  186bc_entry_remove_trash(Actor, Id):-
  187    can_remove(Actor, Id),
  188    bc_entry_slug(Id, Slug),
  189    ds_col_remove(trash, Id),
  190    ds_col_remove_cond(comment, post=Id),
  191    bc_index_remove(Id),
  192    remove_files(Slug),
  193    debug(bc_data_entry, 'removed entry ~p', [Id]).
 bc_entry_restore(+Actor, +Id) is det
Restores the given entry from trash. Require remove permission.
  200bc_entry_restore(Actor, Id):-
  201    can_remove(Actor, Id),
  202    ds_move(trash, Id, entry).
  203
  204can_remove(Actor, Id):-
  205    bc_entry_exists(Id),
  206    remove_access(Actor, Id).
  207
  208remove_access(Actor, Id):-
  209    bc_remove_access_id(Actor, Id), !.
  210
  211remove_access(_, _):-
  212    throw(error(no_access)).
  213
  214% Removes entry files.
  215
  216remove_files(Slug):-
  217    atomic_list_concat([public, '/', Slug], Directory),
  218    (   exists_directory(Directory)
  219    ->  remove_directory(Directory)
  220    ;   true).
 bc_entry_list(+Actor, +Type, -List) is det
Retrieves the list of entries of certain type. Does not include contents and HTML. Sorts by date_updated desc.
  228bc_entry_list(Actor, Type, Sorted):-
  229    ds_find(entry, type=Type, [slug, type, date_published,
  230        date_updated, commenting, published,
  231        title, author, tags], Entries),
  232    include(bc_read_access_entry(Actor), Entries, Filtered),
  233    maplist(attach_comment_count, Filtered, List),
  234    sort_dict(date_updated, desc, List, Sorted),
  235    debug(bc_data_entry, 'retrieved entry list', []).
 bc_trash_list(+Actor, -List) is det
Retrieves the list of entries in trash. Does not include contents and HTML. Sorts by date_updated desc. Only includes these that the user has access to.
  244bc_trash_list(Actor, Sorted):-
  245    ds_all(trash, [slug, type, date_published,
  246        date_updated, commenting, published,
  247        title, author, tags], Entries),
  248    include(bc_remove_access_entry(Actor), Entries, Filtered),
  249    maplist(attach_comment_count, Filtered, List),
  250    sort_dict(date_updated, desc, List, Sorted),
  251    debug(bc_data_entry, 'retrieved trash list', []).
 bc_purge_trash(Actor) is det
Removes all entries from trash that the actor has access to.
  258bc_purge_trash(Actor):-
  259    ds_all(trash, [type, author], Entries),
  260    include(bc_remove_access_entry(Actor), Entries, Filtered),
  261    maplist(ds_id, Filtered, Ids),
  262    maplist(bc_entry_remove_trash(Actor), Ids),
  263    debug(bc_data_entry, 'purged trash', []).
 bc_entry(+Actor, +Id, -Entry) is det
Retrieves a single entry by its Id.
  269bc_entry(Actor, Id, WithCount):-
  270    can_view(Actor, Id),
  271    ds_col_get(entry, Id, [slug, type, date_published, date_updated,
  272        commenting, published, title, author,
  273        content, description, content_type, tags, language], Entry), !,
  274    attach_comment_count(Entry, WithCount),
  275    debug(bc_data_entry, 'retrieved entry ~p', [Id]).
 bc_entry_info(+Actor, +Id, -Entry) is det
Retrieves a single entry by its Id. Does not include the content field.
  282bc_entry_info(Actor, Id, WithCount):-
  283    can_view(Actor, Id),
  284    ds_col_get(entry, Id, [slug, type, date_published, date_updated,
  285        commenting, published, title, author,
  286        description, content_type, tags, language], Entry), !,
  287    attach_comment_count(Entry, WithCount),
  288    debug(bc_data_entry, 'retrieved entry ~p info', [Id]).
  289
  290can_view(Actor, Id):-
  291    bc_entry_exists(Id),
  292    view_access(Actor, Id).
  293
  294view_access(Actor, Id):-
  295    bc_read_access_id(Actor, Id), !.
  296
  297view_access(_, _):-
  298    throw(error(no_access)).
  299
  300% Attaches comment count to the entry.
  301
  302attach_comment_count(EntryIn, EntryOut):-
  303    ds_id(EntryIn, Id),
  304    ds_find(comment, post=Id, [post], List),
  305    length(List, Count),
  306    put_dict(_{ comments: Count }, EntryIn, EntryOut)