View | Details | Raw Unified | Return to bug 32607
Collapse All | Expand All

(-)a/admin/record-sources.pl (+37 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2023 Theke Solutions
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use CGI        qw ( -utf8 );
23
use C4::Auth   qw( get_template_and_user );
24
use C4::Output qw( output_html_with_http_headers );
25
26
my $query = CGI->new;
27
28
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
29
    {
30
        template_name => "admin/record-sources.tt",
31
        query         => $query,
32
        type          => "intranet",
33
        flagsrequired => { parameters => 'manage_record_sources' },
34
    }
35
);
36
37
output_html_with_http_headers $query, $cookie, $template->output;
(-)a/debian/templates/apache-shared-intranet.conf (+2 lines)
Lines 13-18 ScriptAlias /search "/usr/share/koha/intranet/cgi-bin/catalogue/search.pl" Link Here
13
13
14
# Protect dev package install
14
# Protect dev package install
15
RewriteEngine on
15
RewriteEngine on
16
16
RewriteRule ^/cgi-bin/koha/(C4|debian|etc|installer/data|install_misc|Koha|misc|selenium|t|test|tmp|xt)/|\.PL$ /notfound [PT]
17
RewriteRule ^/cgi-bin/koha/(C4|debian|etc|installer/data|install_misc|Koha|misc|selenium|t|test|tmp|xt)/|\.PL$ /notfound [PT]
17
18
18
RewriteRule ^/bib/([^\/]*)/?$ /cgi-bin/koha/catalogue/detail.pl?biblionumber=$1 [PT]
19
RewriteRule ^/bib/([^\/]*)/?$ /cgi-bin/koha/catalogue/detail.pl?biblionumber=$1 [PT]
Lines 23-28 RewriteRule ^(.*)_[0-9]{2}\.[0-9]{7}\.(js|css)$ $1.$2 [L] Link Here
23
RewriteRule ^/cgi-bin/koha/erm/.*$ /cgi-bin/koha/erm/erm.pl [PT]
24
RewriteRule ^/cgi-bin/koha/erm/.*$ /cgi-bin/koha/erm/erm.pl [PT]
24
RewriteCond %{REQUEST_URI} !^/cgi-bin/koha/preservation/.*.pl$
25
RewriteCond %{REQUEST_URI} !^/cgi-bin/koha/preservation/.*.pl$
25
RewriteRule ^/cgi-bin/koha/preservation/.*$ /cgi-bin/koha/preservation/home.pl [PT]
26
RewriteRule ^/cgi-bin/koha/preservation/.*$ /cgi-bin/koha/preservation/home.pl [PT]
27
RewriteRule ^/cgi-bin/koha/admin/record-sources(.*)?$ /cgi-bin/koha/admin/record-sources.pl$1 [PT]
26
28
27
Alias "/api" "/usr/share/koha/api"
29
Alias "/api" "/usr/share/koha/api"
28
<Directory "/usr/share/koha/api">
30
<Directory "/usr/share/koha/api">
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/admin-home.tt (+4 lines)
Lines 209-214 Link Here
209
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration (Elasticsearch)</a></dt>
209
                        <dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration (Elasticsearch)</a></dt>
210
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields</dd>
210
                        <dd>Manage indexes, facets, and their mappings to MARC fields and subfields</dd>
211
                    [% END %]
211
                    [% END %]
212
                    [% IF ( CAN_user_parameters_manage_record_sources ) %]
213
                        <dt><a href="/cgi-bin/koha/admin/record-sources">Record sources</a></dt>
214
                        <dd>Define record sources to import from</dd>
215
                    [% END %]
212
                </dl>
216
                </dl>
213
            [% END %]
217
            [% END %]
214
218
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/record-sources.tt (+37 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE To %]
3
[% USE Asset %]
4
[% USE KohaDates %]
5
[% USE TablesSettings %]
6
[% USE AuthorisedValues %]
7
[% SET footerjs = 1 %]
8
[% PROCESS 'i18n.inc' %]
9
[% INCLUDE 'doc-head-open.inc' %]
10
<title>
11
    Record sources &rsaquo; Koha
12
</title>
13
[% INCLUDE 'doc-head-close.inc' %]
14
</head>
15
16
<body id="record_sources" class="record_sources">
17
[% WRAPPER 'header.inc' %]
18
    [% INCLUDE 'prefs-admin-search.inc' %]
19
[% END %]
20
21
<div id="record-source"> <!-- this is closed in intranet-bottom.inc -->
22
23
[% MACRO jsinclude BLOCK %]
24
    [% INCLUDE 'datatables.inc' %]
25
    [% INCLUDE 'columns_settings.inc' %]
26
    [% INCLUDE 'js-patron-format.inc' %]
27
    [% INCLUDE 'js-date-format.inc' %]
28
29
    <script>
30
      const RESTOAuth2ClientCredentials = [% IF Koha.Preference('RESTOAuth2ClientCredentials') %]true[% ELSE %]false[% END %];
31
    </script>
32
33
    [% Asset.js("js/vue/dist/record-source.js") | $raw %]
34
35
[% END %]
36
37
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Page.vue (+55 lines)
Line 0 Link Here
1
<template>
2
    <div :id="id">
3
        <div id="sub-header">
4
            <Breadcrumbs></Breadcrumbs>
5
            <Help />
6
        </div>
7
        <div class="main container-fluid">
8
            <div class="row">
9
                <template v-if="leftMenu">
10
                    <div class="col-sm-10 col-sm-push-2">
11
                        <Dialog></Dialog>
12
                        <slot></slot>
13
                    </div>
14
                    <div class="col-sm-2 col-sm-pull-10">
15
                        <LeftMenu :title="title"></LeftMenu>
16
                    </div>
17
                </template>
18
                <template v-else>
19
                    <div
20
                        class="col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2"
21
                    >
22
                        <Dialog></Dialog>
23
                        <slot></slot>
24
                    </div>
25
                </template>
26
            </div>
27
        </div>
28
    </div>
29
</template>
30
31
<script>
32
import LeftMenu from "./LeftMenu.vue"
33
import Breadcrumbs from "./Breadcrumbs.vue"
34
import Help from "./Help.vue"
35
import Dialog from "./Dialog.vue"
36
export default {
37
    name: "Page",
38
    components: {
39
        LeftMenu,
40
        Dialog,
41
        Breadcrumbs,
42
        Help,
43
    },
44
    props: {
45
        id: String,
46
        title: String,
47
        leftMenu: {
48
            type: Boolean,
49
            default: true,
50
        },
51
    },
52
}
53
</script>
54
55
<style></style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/RecordSources/Edit.vue (+163 lines)
Line 0 Link Here
1
<template>
2
    <div v-if="!initialized">{{ $__("Loading") }}</div>
3
    <div v-else id="record_source_edit">
4
        <h1>{{ title }}</h1>
5
        <form @submit="processSubmit">
6
            <fieldset class="rows">
7
                <ol>
8
                    <li>
9
                        <label class="required" for="name">
10
                            {{ $__("Name") }}:
11
                        </label>
12
                        <input
13
                            id="name"
14
                            v-model="row.name"
15
                            :placeholder="$__('Name')"
16
                            required
17
                        />
18
                        <span class="required">{{ $__("Required") }}</span>
19
                    </li>
20
                    <li>
21
                        <label :for="`user_id`" class="required"
22
                            >{{ $__("User") }}:</label
23
                        >
24
                        <span class="user">
25
                            {{ patron_str }}
26
                        </span>
27
                        (<a
28
                            href="#"
29
                            @click="selectUser()"
30
                            class="btn btn-default"
31
                            >{{ $__("Select user") }}</a
32
                        >)
33
                        <span class="required">{{ $__("Required") }}</span>
34
                    </li>
35
                    <input
36
                        type="hidden"
37
                        name="selected_patron_id"
38
                        id="selected_patron_id"
39
                    />
40
                </ol>
41
            </fieldset>
42
            <fieldset class="action">
43
                <input type="submit" :value="$__('Submit')" />
44
                <router-link
45
                    to="../record-sources"
46
                    role="button"
47
                    class="cancel"
48
                    >{{ $__("Cancel") }}</router-link
49
                >
50
            </fieldset>
51
        </form>
52
    </div>
53
</template>
54
55
<script>
56
import { inject } from "vue"
57
import { RecordSourcesClient } from "../../fetch/record-sources"
58
import { APIClient } from "../../fetch/api-client.js"
59
let {
60
    patron: { patrons },
61
} = APIClient
62
63
export default {
64
    name: "Edit",
65
    setup() {
66
        const { record_source } = new RecordSourcesClient()
67
        const { setMessage } = inject("mainStore")
68
        return {
69
            setMessage,
70
            api: record_source,
71
        }
72
    },
73
    data() {
74
        return {
75
            row: {
76
                name: "",
77
            },
78
            patron_str: "",
79
            initialized: false,
80
        }
81
    },
82
    methods: {
83
        processSubmit(event) {
84
            event.preventDefault()
85
            const _hasValue = value => {
86
                return value !== undefined && value !== null && value !== ""
87
            }
88
            if (!_hasValue(this.row.name) || !_hasValue(this.row.patron_id))
89
                return false
90
            let response
91
            if (this.row.record_source_id) {
92
                const { record_source_id: id, ...row } = this.row
93
                response = this.api
94
                    .update({ id, row })
95
                    .then(() => this.$__("Record source updated!"))
96
            } else {
97
                response = this.api
98
                    .create({ row: this.row })
99
                    .then(() => this.$__("Record source created!"))
100
            }
101
            return response.then(responseMessage => {
102
                this.setMessage(responseMessage)
103
                return this.$router.push({ path: "../record-sources" })
104
            })
105
        },
106
        selectUser() {
107
            let select_user_window = window.open(
108
                "/cgi-bin/koha/members/search.pl?columns=cardnumber,name,category,branch,action&selection_type=select",
109
                "PatronPopup",
110
                "width=740,height=450,location=yes,toolbar=no," +
111
                    "scrollbars=yes,resize=yes"
112
            )
113
            // This is a bit dirty, the "select user" window should be rewritten and be a Vue component
114
            // but that's not for now...
115
            select_user_window.addEventListener(
116
                "beforeunload",
117
                this.newUserSelected,
118
                false
119
            )
120
        },
121
        newUserSelected() {
122
            this.row.patron_id =
123
                document.getElementById("selected_patron_id").value
124
            this.getUserData()
125
        },
126
        getUserData() {
127
            if (this.row.patron_id === null) {
128
                this.patron_str = ""
129
            }
130
            else{
131
                patrons.get(this.row.patron_id).then(patron => {
132
                    this.patron_str = $patron_to_html(patron)
133
                })
134
            }
135
        },
136
    },
137
    created() {
138
        const { id } = this.$route.params
139
        if (id !== undefined) {
140
            this.api
141
                .get({
142
                    id,
143
                })
144
                .then(response => {
145
                    Object.keys(response).forEach(key => {
146
                        this.row[key] = response[key]
147
                    })
148
                    this.getUserData()
149
                    this.initialized = true
150
                })
151
        } else {
152
            this.initialized = true
153
        }
154
    },
155
    computed: {
156
        title() {
157
            if (!this.row || !this.row.record_source_id)
158
                return this.$__("Add record source")
159
            return this.$__("Edit %s").format(this.row.name)
160
        },
161
    },
162
}
163
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/RecordSources/List.vue (+142 lines)
Line 0 Link Here
1
<template>
2
    <div v-if="!initialized">{{ $__("Loading") }}</div>
3
    <div v-else>
4
        <div id="toolbar" class="btn-toolbar">
5
            <div class="btn-group">
6
                <button class="btn btn-default" @click="newRecordSource">
7
                    <i class="fa fa-plus"></i> New record source
8
                </button>
9
            </div>
10
        </div>
11
        <h1>{{ title }}</h1>
12
        <div v-if="record_sources_count > 0" class="page-section">
13
            <KohaTable
14
                ref="table"
15
                v-bind="tableOptions"
16
                @edit="doEdit"
17
                @delete="doRemove"
18
            ></KohaTable>
19
        </div>
20
        <div v-else>
21
            {{ $__("There are no record sources defined") }}
22
        </div>
23
    </div>
24
</template>
25
26
<script>
27
import { useRouter } from "vue-router"
28
import { inject } from "vue"
29
import { RecordSourcesClient } from "../../fetch/record-sources"
30
import KohaTable from "../KohaTable.vue"
31
export default {
32
    name: "List",
33
    data() {
34
        return {
35
            title: this.$__("Record sources"),
36
            tableOptions: {
37
                columns: [
38
                    {
39
                        title: this.$__("Source name"),
40
                        data: "name",
41
                        searchable: true,
42
                    },
43
                    {
44
                        title: this.$__("Patron"),
45
                        searchable: true,
46
                        data: "patron.firstname:patron.surname:patron.middle_name",
47
                        render: (data, type, row) => {
48
                            if ( row.patron === null )
49
                                return ""
50
                            const { firstname, surname, middle_name } =
51
                                row.patron
52
                            return [firstname, middle_name, surname]
53
                                .filter(part => (part || "").trim())
54
                                .join(" ")
55
                        },
56
                    },
57
                    {
58
                        title: this.$__("API token"),
59
                        data: "api_token",
60
                        searchable: true,
61
                    },
62
                ],
63
                options: {
64
                    embed: "patron",
65
                },
66
                actions: {
67
                    "-1": ["edit", "delete"],
68
                },
69
                url: "/api/v1/record_sources",
70
            },
71
            initialized: false,
72
            record_sources_count: 0,
73
        }
74
    },
75
    setup() {
76
        const { setWarning, setMessage, setError, setConfirmationDialog } =
77
            inject("mainStore")
78
        const { record_source } = new RecordSourcesClient()
79
        return {
80
            setWarning,
81
            setMessage,
82
            setError,
83
            setConfirmationDialog,
84
            api: record_source,
85
        }
86
    },
87
    beforeRouteEnter(to, from, next) {
88
        next(vm => {
89
            vm.getRecordSourcesCount().then(() => (vm.initialized = true))
90
        })
91
    },
92
    methods: {
93
        async getRecordSourcesCount() {
94
            const count = await this.api.count()
95
            this.record_sources_count = count
96
        },
97
        newRecordSource() {
98
            this.$router.push({ path: "record-sources/add" })
99
        },
100
        doEdit(data) {
101
            this.$router.push({
102
                path: `record-sources/${data.record_source_id}`,
103
                props: {
104
                    data,
105
                },
106
            })
107
        },
108
        doRemove(data, dt) {
109
            this.setConfirmationDialog(
110
                {
111
                    title: this.$__(
112
                        "Are you sure you want to remove this record source?"
113
                    ),
114
                    message: data.name,
115
                    accept_label: this.$__("Yes, remove"),
116
                    cancel_label: this.$__("No, do not remove"),
117
                },
118
                () => {
119
                    this.api
120
                        .delete({
121
                            id: data.record_source_id,
122
                        })
123
                        .then(response => {
124
                            if (response) {
125
                                this.setMessage(
126
                                    this.$__(
127
                                        "Record source '%s' removed"
128
                                    ).format(data.name),
129
                                    true
130
                                )
131
                                dt.draw()
132
                            }
133
                        })
134
                }
135
            )
136
        },
137
    },
138
    components: {
139
        KohaTable,
140
    },
141
}
142
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/RecordSources/Main.vue (+17 lines)
Line 0 Link Here
1
<template>
2
    <Page :left-menu="false">
3
        <router-view></router-view>
4
    </Page>
5
</template>
6
7
<script>
8
import Page from "../Page.vue"
9
export default {
10
    name: "Main",
11
    components: {
12
        Page,
13
    },
14
}
15
</script>
16
17
<style></style>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/fetch/record-sources.js (+59 lines)
Line 0 Link Here
1
import HttpClient from "./http-client";
2
3
export class RecordSourcesClient extends HttpClient {
4
    constructor() {
5
        super({
6
            baseURL: "/api/v1/record_sources",
7
        });
8
    }
9
10
    get record_source() {
11
        return {
12
            create: ({ row, headers = {} }) => {
13
                const requestHeaders = {
14
                    ...headers,
15
                    "Content-Type": "application/json",
16
                };
17
                return this.post({
18
                    endpoint: "",
19
                    headers: requestHeaders,
20
                    body: JSON.stringify(row),
21
                });
22
            },
23
            delete: ({ id, headers = {} }) => {
24
                return this.delete({
25
                    endpoint: "/" + id,
26
                    headers,
27
                });
28
            },
29
            update: ({ id, headers = {}, row }) => {
30
                const requestHeaders = {
31
                    ...headers,
32
                    "Content-Type": "application/json",
33
                };
34
                return this.put({
35
                    endpoint: "/" + id,
36
                    headers: requestHeaders,
37
                    body: JSON.stringify(row),
38
                });
39
            },
40
            get: ({ id, headers = {} }) => {
41
                return this.get({
42
                    endpoint: "/" + id,
43
                    headers,
44
                });
45
            },
46
            count: (query = {}) => {
47
                return this.count({
48
                    endpoint:
49
                        "?" +
50
                        new URLSearchParams({
51
                            _page: 1,
52
                            _per_page: 1,
53
                            ...(query && { q: JSON.stringify(query) }),
54
                        }),
55
                });
56
            },
57
        };
58
    }
59
}
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/record-sources.ts (+54 lines)
Line 0 Link Here
1
import { createApp } from "vue";
2
import { createPinia } from "pinia";
3
import { createWebHistory, createRouter } from "vue-router";
4
5
import { library } from "@fortawesome/fontawesome-svg-core";
6
import {
7
    faPlus,
8
    faMinus,
9
    faPencil,
10
    faTrash,
11
    faSpinner,
12
} from "@fortawesome/free-solid-svg-icons";
13
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
14
import vSelect from "vue-select";
15
import { useNavigationStore } from "../stores/navigation";
16
import { useMainStore } from "../stores/main";
17
import routesDef from "../routes/record-sources";
18
19
library.add(faPlus, faMinus, faPencil, faTrash, faSpinner);
20
21
const pinia = createPinia();
22
const navigationStore = useNavigationStore(pinia);
23
const mainStore = useMainStore(pinia);
24
const { removeMessages } = mainStore;
25
const { setRoutes } = navigationStore;
26
const routes = setRoutes(routesDef);
27
28
const router = createRouter({
29
    history: createWebHistory(),
30
    linkExactActiveClass: "current",
31
    routes,
32
});
33
34
import App from "../components/RecordSources/Main.vue";
35
import i18n from "../i18n";
36
37
const app = createApp(App);
38
39
const rootComponent = app
40
    .use(i18n)
41
    .use(pinia)
42
    .use(router)
43
    .component("font-awesome-icon", FontAwesomeIcon)
44
    .component("v-select", vSelect);
45
46
app.config.unwrapInjectedRef = true;
47
app.provide("mainStore", mainStore);
48
app.provide("navigationStore", navigationStore);
49
app.mount("#record-source");
50
51
router.beforeEach(to => {
52
    navigationStore.$patch({ current: to.matched, params: to.params || {} });
53
    removeMessages(); // This will actually flag the messages as displayed already
54
});
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/record-sources.js (+38 lines)
Line 0 Link Here
1
import { markRaw } from "vue";
2
import Edit from "../components/RecordSources/Edit.vue";
3
import List from "../components/RecordSources/List.vue";
4
import { $__ } from "../i18n";
5
6
export default {
7
    title: $__("Administration"),
8
    path: "",
9
    href: "/cgi-bin/koha/admin/admin-home.pl",
10
    BeforeUnloadEvent() {
11
        window.location.href = "/cgi-bin/koha/admin/admin-home.pl";
12
    },
13
    is_base: true,
14
    is_default: true,
15
    children: [
16
        {
17
            title: $__("Record sources"),
18
            path: "/cgi-bin/koha/admin/record-sources",
19
            children: [
20
                {
21
                    title: $__("List"),
22
                    path: "",
23
                    component: markRaw(List),
24
                },
25
                {
26
                    title: $__("Add record source"),
27
                    path: "add",
28
                    component: markRaw(Edit),
29
                },
30
                {
31
                    title: $__("Edit record source"),
32
                    path: ":id",
33
                    component: markRaw(Edit),
34
                },
35
            ],
36
        },
37
    ],
38
};
(-)a/webpack.config.js (-1 / +1 lines)
Lines 7-12 module.exports = { Link Here
7
  entry: {
7
  entry: {
8
    erm: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/erm.ts",
8
    erm: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/erm.ts",
9
    preservation: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts",
9
    preservation: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts",
10
    "record-source": "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/record-sources.ts",
10
  },
11
  },
11
  output: {
12
  output: {
12
    filename: "[name].js",
13
    filename: "[name].js",
13
- 

Return to bug 32607