Merge branch 'master' into Develop

# Conflicts:
#	cps/opds.py
#	cps/server.py
#	cps/web.py
This commit is contained in:
Ozzie Isaacs 2023-02-05 12:10:01 +01:00
commit 4b7a0f3662
87 changed files with 5948 additions and 5978 deletions

1
.gitattributes vendored
View File

@ -1,4 +1,5 @@
constants.py ident export-subst constants.py ident export-subst
/test export-ignore /test export-ignore
/library export-ignore
cps/static/css/libs/* linguist-vendored cps/static/css/libs/* linguist-vendored
cps/static/js/libs/* linguist-vendored cps/static/js/libs/* linguist-vendored

1
.gitignore vendored
View File

@ -28,6 +28,7 @@ cps/cache
.idea/ .idea/
*.bak *.bak
*.log.* *.log.*
.key
settings.yaml settings.yaml
gdrive_credentials gdrive_credentials

1
.key
View File

@ -1 +0,0 @@
onLmA_LND5S8jNSvi8nNSGwevE13f7t8pW-wgWAXZgo=

View File

@ -52,7 +52,8 @@ In the Wiki there are also examples for: a [manual installation](https://github.
Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog \ Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog \
Login with default admin login \ Login with default admin login \
Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button \ If you don't have a Calibre database already, this [database](https://github.com/janeczku/calibre-web/blob/master/library/metadata.db) can be used. **IMPORTATNT** Please move the database out of the calibre-web folder structure, as it will be overwritten during update. \
Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button. \
Optionally a Google Drive can be used to host the calibre library [-> Using Google Drive integration](https://github.com/janeczku/calibre-web/wiki/G-Drive-Setup#using-google-drive-integration) \ Optionally a Google Drive can be used to host the calibre library [-> Using Google Drive integration](https://github.com/janeczku/calibre-web/wiki/G-Drive-Setup#using-google-drive-integration) \
Afterwards you can configure your Calibre-Web instance ([Basic Configuration](https://github.com/janeczku/calibre-web/wiki/Configuration#basic-configuration) and [UI Configuration](https://github.com/janeczku/calibre-web/wiki/Configuration#ui-configuration) on admin page) Afterwards you can configure your Calibre-Web instance ([Basic Configuration](https://github.com/janeczku/calibre-web/wiki/Configuration#basic-configuration) and [UI Configuration](https://github.com/janeczku/calibre-web/wiki/Configuration#ui-configuration) on admin page)

View File

@ -21,9 +21,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask_login import LoginManager from flask_login import LoginManager, confirm_login
from flask import session from flask import session, current_app
from flask_login.utils import decode_cookie
from flask_login.signals import user_loaded_from_cookie
class MyLoginManager(LoginManager): class MyLoginManager(LoginManager):
def _session_protection_failed(self): def _session_protection_failed(self):
@ -33,3 +34,19 @@ class MyLoginManager(LoginManager):
and _session.get('csrf_token', None))) and ident != _session.get('_id', None): and _session.get('csrf_token', None))) and ident != _session.get('_id', None):
return super(). _session_protection_failed() return super(). _session_protection_failed()
return False return False
def _load_user_from_remember_cookie(self, cookie):
user_id = decode_cookie(cookie)
if user_id is not None:
session["_user_id"] = user_id
session["_fresh"] = False
user = None
if self._user_callback:
user = self._user_callback(user_id)
if user is not None:
app = current_app._get_current_object()
user_loaded_from_cookie.send(app, user=user)
# if session was restored from remember me cookie make login valid
confirm_login()
return user
return None

View File

@ -33,7 +33,7 @@ from datetime import time as datetime_time
from functools import wraps from functools import wraps
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response
from flask_login import login_required, current_user, logout_user, confirm_login from flask_login import login_required, current_user, logout_user
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_babel import get_locale, format_time, format_datetime, format_timedelta from flask_babel import get_locale, format_time, format_datetime, format_timedelta
from flask import session as flask_session from flask import session as flask_session
@ -101,21 +101,16 @@ def admin_required(f):
@admi.before_app_request @admi.before_app_request
def before_request(): def before_request():
# make remember me function work
if current_user.is_authenticated:
confirm_login()
if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path: if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path:
logout_user() logout_user()
g.constants = constants g.constants = constants
g.user = current_user # g.user = current_user
g.google_site_verification = os.getenv('GOOGLE_SITE_VERIFICATION','') g.google_site_verification = os.getenv('GOOGLE_SITE_VERIFICATION','')
g.allow_registration = config.config_public_reg g.allow_registration = config.config_public_reg
g.allow_anonymous = config.config_anonbrowse g.allow_anonymous = config.config_anonbrowse
g.allow_upload = config.config_uploading g.allow_upload = config.config_uploading
g.current_theme = config.config_theme g.current_theme = config.config_theme
g.config_authors_max = config.config_authors_max g.config_authors_max = config.config_authors_max
g.shelves_access = ub.session.query(ub.Shelf).filter(
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
if '/static/' not in request.path and not config.db_configured and \ if '/static/' not in request.path and not config.db_configured and \
request.endpoint not in ('admin.ajax_db_config', request.endpoint not in ('admin.ajax_db_config',
'admin.simulatedbchange', 'admin.simulatedbchange',

View File

@ -1,7 +1,8 @@
from babel import negotiate_locale from babel import negotiate_locale
from flask_babel import Babel, Locale from flask_babel import Babel, Locale
from babel.core import UnknownLocaleError from babel.core import UnknownLocaleError
from flask import request, g from flask import request
from flask_login import current_user
from . import logger from . import logger
@ -11,10 +12,10 @@ babel = Babel()
def get_locale(): def get_locale():
# if a user is logged in, use the locale from the user settings # if a user is logged in, use the locale from the user settings
user = getattr(g, 'user', None) if current_user is not None and hasattr(current_user, "locale"):
if user is not None and hasattr(user, "locale"): # if the account is the guest account bypass the config lang settings
if user.name != 'Guest': # if the account is the guest account bypass the config lang settings if current_user.name != 'Guest':
return user.locale return current_user.locale
preferred = list() preferred = list()
if request.accept_languages: if request.accept_languages:

View File

@ -163,7 +163,7 @@ def selected_roles(dictionary):
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, ' BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
'series_id, languages, publisher, pubdate, identifiers') 'series_id, languages, publisher, pubdate, identifiers')
STABLE_VERSION = {'version': '0.6.19'} STABLE_VERSION = {'version': '0.6.20 Beta'}
NIGHTLY_VERSION = dict() NIGHTLY_VERSION = dict()
NIGHTLY_VERSION[0] = '$Format:%H$' NIGHTLY_VERSION[0] = '$Format:%H$'

View File

@ -22,41 +22,26 @@
import datetime import datetime
from urllib.parse import unquote_plus from urllib.parse import unquote_plus
from functools import wraps
from flask import Blueprint, request, render_template, Response, g, make_response, abort from flask import Blueprint, request, render_template, make_response, abort
from flask_login import current_user from flask_login import current_user
from flask_babel import get_locale from flask_babel import get_locale
from flask_babel import gettext as _
from sqlalchemy.sql.expression import func, text, or_, and_, true from sqlalchemy.sql.expression import func, text, or_, and_, true
from sqlalchemy.exc import InvalidRequestError, OperationalError from sqlalchemy.exc import InvalidRequestError, OperationalError
from werkzeug.security import check_password_hash
from . import constants, logger, config, db, calibre_db, ub, services, isoLanguages, limiter from . import logger, config, db, calibre_db, ub, isoLanguages
from .usermanagement import requires_basic_auth_if_no_ano
from .helper import get_download_link, get_book_cover from .helper import get_download_link, get_book_cover
from .pagination import Pagination from .pagination import Pagination
from .web import render_read_books from .web import render_read_books
from .usermanagement import load_user_from_request
from flask_babel import gettext as _
from flask_limiter import RateLimitExceeded
opds = Blueprint('opds', __name__) opds = Blueprint('opds', __name__)
log = logger.create() log = logger.create()
def requires_basic_auth_if_no_ano(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if config.config_anonbrowse != 1:
if not auth or auth.type != 'basic' or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
if config.config_login_type == constants.LOGIN_LDAP and services.ldap and config.config_anonbrowse != 1:
return services.ldap.basic_auth_required(f)
return decorated
@opds.route("/opds/") @opds.route("/opds/")
@opds.route("/opds") @opds.route("/opds")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
@ -356,7 +341,8 @@ def feed_languages(book_id):
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_shelfindex(): def feed_shelfindex():
off = request.args.get("offset") or 0 off = request.args.get("offset") or 0
shelf = g.shelves_access shelf = ub.session.query(ub.Shelf).filter(
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
number = len(shelf) number = len(shelf)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
number) number)
@ -403,11 +389,7 @@ def feed_shelf(book_id):
@opds.route("/opds/download/<book_id>/<book_format>/") @opds.route("/opds/download/<book_id>/<book_format>/")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def opds_download_link(book_id, book_format): def opds_download_link(book_id, book_format):
# I gave up with this: With enabled ldap login, the user doesn't get logged in, therefore it's always guest if not current_user.role_download():
# workaround, loading the user from the request and checking its download rights here
# in case of anonymous browsing user is None
user = load_user_from_request(request) or current_user
if not user.role_download():
return abort(403) return abort(403)
if "Kobo" in request.headers.get('User-Agent'): if "Kobo" in request.headers.get('User-Agent'):
client = "kobo" client = "kobo"
@ -479,32 +461,6 @@ def feed_search(term):
return render_xml_template('feed.xml', searchterm="") return render_xml_template('feed.xml', searchterm="")
def check_auth(username, password):
try:
limiter.check()
except RateLimitExceeded:
return abort(429) # False
try:
username = username.encode('windows-1252')
except UnicodeEncodeError:
username = username.encode('utf-8')
user = ub.session.query(ub.User).filter(func.lower(ub.User.name) ==
username.decode('utf-8').lower()).first()
if bool(user and check_password_hash(str(user.password), password)):
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
return True
else:
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
log.warning('OPDS Login failed for user "%s" IP-address: %s', username.decode('utf-8'), ip_address)
return False
def authenticate():
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def render_xml_template(*args, **kwargs): def render_xml_template(*args, **kwargs):
# ToDo: return time in current timezone similar to %z # ToDo: return time in current timezone similar to %z

View File

@ -20,11 +20,13 @@ from flask import render_template, g, abort, request
from flask_babel import gettext as _ from flask_babel import gettext as _
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from flask_login import current_user from flask_login import current_user
from sqlalchemy.sql.expression import or_
from . import config, constants, logger from . import config, constants, logger, ub
from .ub import User from .ub import User
log = logger.create() log = logger.create()
def get_sidebar_config(kwargs=None): def get_sidebar_config(kwargs=None):
@ -45,12 +47,12 @@ def get_sidebar_config(kwargs=None):
"show_text": _('Show Hot Books'), "config_show": True}) "show_text": _('Show Hot Books'), "config_show": True})
if current_user.role_admin(): if current_user.role_admin():
sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.download_list', sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.download_list',
"id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not g.user.is_anonymous), "id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not current_user.is_anonymous),
"page": "download", "show_text": _('Show Downloaded Books'), "page": "download", "show_text": _('Show Downloaded Books'),
"config_show": content}) "config_show": content})
else: else:
sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.books_list', sidebar.append({"glyph": "glyphicon-download", "text": _('Downloaded Books'), "link": 'web.books_list',
"id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not g.user.is_anonymous), "id": "download", "visibility": constants.SIDEBAR_DOWNLOAD, 'public': (not current_user.is_anonymous),
"page": "download", "show_text": _('Show Downloaded Books'), "page": "download", "show_text": _('Show Downloaded Books'),
"config_show": content}) "config_show": content})
sidebar.append( sidebar.append(
@ -58,11 +60,11 @@ def get_sidebar_config(kwargs=None):
"visibility": constants.SIDEBAR_BEST_RATED, 'public': True, "page": "rated", "visibility": constants.SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
"show_text": _('Show Top Rated Books'), "config_show": True}) "show_text": _('Show Top Rated Books'), "config_show": True})
sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read", sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read",
"visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not current_user.is_anonymous),
"page": "read", "show_text": _('Show Read and Unread'), "config_show": content}) "page": "read", "show_text": _('Show Read and Unread'), "config_show": content})
sidebar.append( sidebar.append(
{"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread", {"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread",
"visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread", "visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not current_user.is_anonymous), "page": "unread",
"show_text": _('Show unread'), "config_show": False}) "show_text": _('Show unread'), "config_show": False})
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand", sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand",
"visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover", "visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover",
@ -81,7 +83,7 @@ def get_sidebar_config(kwargs=None):
"visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher", "visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher",
"show_text": _('Show Publisher Section'), "config_show":True}) "show_text": _('Show Publisher Section'), "config_show":True})
sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang", sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang",
"visibility": constants.SIDEBAR_LANGUAGE, 'public': (g.user.filter_language() == 'all'), "visibility": constants.SIDEBAR_LANGUAGE, 'public': (current_user.filter_language() == 'all'),
"page": "language", "page": "language",
"show_text": _('Show Language Section'), "config_show": True}) "show_text": _('Show Language Section'), "config_show": True})
sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate", sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate",
@ -92,13 +94,16 @@ def get_sidebar_config(kwargs=None):
"page": "format", "show_text": _('Show File Formats Section'), "config_show": True}) "page": "format", "show_text": _('Show File Formats Section'), "config_show": True})
sidebar.append( sidebar.append(
{"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived", {"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
"visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived", "visibility": constants.SIDEBAR_ARCHIVED, 'public': (not current_user.is_anonymous), "page": "archived",
"show_text": _('Show Archived Books'), "config_show": content}) "show_text": _('Show Archived Books'), "config_show": content})
if not simple: if not simple:
sidebar.append( sidebar.append(
{"glyph": "glyphicon-th-list", "text": _('Books List'), "link": 'web.books_table', "id": "list", {"glyph": "glyphicon-th-list", "text": _('Books List'), "link": 'web.books_table', "id": "list",
"visibility": constants.SIDEBAR_LIST, 'public': (not g.user.is_anonymous), "page": "list", "visibility": constants.SIDEBAR_LIST, 'public': (not current_user.is_anonymous), "page": "list",
"show_text": _('Show Books List'), "config_show": content}) "show_text": _('Show Books List'), "config_show": content})
g.shelves_access = ub.session.query(ub.Shelf).filter(
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
return sidebar, simple return sidebar, simple

View File

@ -35,12 +35,13 @@ search = Blueprint('search', __name__)
log = logger.create() log = logger.create()
@search.route("/search", methods=["GET"]) @search.route("/search", methods=["POST"])
@login_required_if_no_ano @login_required_if_no_ano
def simple_search(): def simple_search():
term = request.args.get("query") term = dict(request.form).get("query")
if term: if term:
return redirect(url_for('web.books_list', data="search", sort_param='stored', query=term.strip())) flask_session['query'] = json.dumps(term.strip())
return redirect(url_for('web.books_list', data="search", sort_param='stored', query="")) # term.strip()
else: else:
return render_title_template('search.html', return render_title_template('search.html',
searchterm="", searchterm="",

View File

@ -269,7 +269,7 @@ class WebServer(object):
@staticmethod @staticmethod
def shutdown_scheduler(): def shutdown_scheduler():
scheduler = BackgroundScheduler() scheduler = BackgroundScheduler()
if scheduler: # and not scheduler.scheduler.STATE_STOPPED: if scheduler:
scheduler.scheduler.shutdown() scheduler.scheduler.shutdown()
def _killServer(self, __, ___): def _killServer(self, __, ___):

View File

@ -140,6 +140,7 @@ table .bg-dark-danger a { color: #fff; }
.container-fluid .book { .container-fluid .book {
margin-top: 20px; margin-top: 20px;
max-width: 180px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }

File diff suppressed because one or more lines are too long

View File

@ -364,12 +364,6 @@ $(function() {
layoutMode : "fitRows" layoutMode : "fitRows"
}); });
$(".grid").isotope({
// options
itemSelector : ".grid-item",
layoutMode : "fitColumns"
});
if ($(".load-more").length && $(".next").length) { if ($(".load-more").length && $(".next").length) {
var $loadMore = $(".load-more .row").infiniteScroll({ var $loadMore = $(".load-more .row").infiniteScroll({
debug: false, debug: false,

View File

@ -6,7 +6,7 @@
<!-- Always use full-sized image for the book edit page --> <!-- Always use full-sized image for the book edit page -->
<img id="detailcover" title="{{book.title}}" src="{{url_for('web.get_cover', book_id=book.id, resolution='og', c=book|last_modified)}}" /> <img id="detailcover" title="{{book.title}}" src="{{url_for('web.get_cover', book_id=book.id, resolution='og', c=book|last_modified)}}" />
</div> </div>
{% if g.user.role_delete_books() %} {% if current_user.role_delete_books() %}
<div class="text-center"> <div class="text-center">
<button type="button" class="btn btn-danger" id="delete" data-toggle="modal" data-delete-id="{{ book.id }}" data-target="#deleteModal">{{_("Delete Book")}}</button> <button type="button" class="btn btn-danger" id="delete" data-toggle="modal" data-delete-id="{{ book.id }}" data-target="#deleteModal">{{_("Delete Book")}}</button>
</div> </div>
@ -99,7 +99,7 @@
<label for="rating">{{_('Rating')}}</label> <label for="rating">{{_('Rating')}}</label>
<input type="number" name="rating" id="rating" class="rating input-lg" data-clearable="" value="{% if book.ratings %}{{(book.ratings[0].rating / 2)|int}}{% endif %}"> <input type="number" name="rating" id="rating" class="rating input-lg" data-clearable="" value="{% if book.ratings %}{{(book.ratings[0].rating / 2)|int}}{% endif %}">
</div> </div>
{% if g.user.role_upload() and g.allow_upload %} {% if current_user.role_upload() and g.allow_upload %}
<div class="form-group"> <div class="form-group">
<label for="cover_url">{{_('Fetch Cover from URL (JPEG - Image will be downloaded and stored in database)')}}</label> <label for="cover_url">{{_('Fetch Cover from URL (JPEG - Image will be downloaded and stored in database)')}}</label>
<input type="text" class="form-control" name="cover_url" id="cover_url" value=""> <input type="text" class="form-control" name="cover_url" id="cover_url" value="">
@ -196,7 +196,7 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if g.user.role_upload() and g.allow_upload %} {% if current_user.role_upload() and g.allow_upload %}
<div role="group" aria-label="Upload new book format"> <div role="group" aria-label="Upload new book format">
<label class="btn btn-primary btn-file" for="btn-upload-format">{{ _('Upload Format') }}</label> <label class="btn btn-primary btn-file" for="btn-upload-format">{{ _('Upload Format') }}</label>
<div class="upload-format-input-text" id="upload-format"></div> <div class="upload-format-input-text" id="upload-format"></div>
@ -219,7 +219,7 @@
{% endblock %} {% endblock %}
{% block modal %} {% block modal %}
{{ delete_book() }} {{ delete_book(current_user.role_delete_books()) }}
{{ delete_confirm_modal() }} {{ delete_confirm_modal() }}
<div class="modal fade" id="metaModal" tabindex="-1" role="dialog" aria-labelledby="metaModalLabel"> <div class="modal fade" id="metaModal" tabindex="-1" role="dialog" aria-labelledby="metaModalLabel">
@ -291,7 +291,7 @@
'description': {{_('Description')|safe|tojson}}, 'description': {{_('Description')|safe|tojson}},
'source': {{_('Source')|safe|tojson}}, 'source': {{_('Source')|safe|tojson}},
}; };
var language = '{{ g.user.locale }}'; var language = '{{ current_user.locale }}';
$("#add-identifier-line").click(function() { $("#add-identifier-line").click(function() {
// create a random identifier type to have a valid name in form. This will not be used when dealing with the form // create a random identifier type to have a valid name in form. This will not be used when dealing with the form
@ -313,8 +313,8 @@
<script src="{{ url_for('static', filename='js/get_meta.js') }}"></script> <script src="{{ url_for('static', filename='js/get_meta.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/tinymce/tinymce.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/tinymce/tinymce.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/bootstrap-datepicker.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/bootstrap-datepicker.min.js') }}"></script>
{% if not g.user.locale == 'en' %} {% if not current_user.locale == 'en' %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + g.user.locale + '.min.js') }}" charset="UTF-8"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + current_user.locale + '.min.js') }}" charset="UTF-8"></script>
{% endif %} {% endif %}
<script src="{{ url_for('static', filename='js/edit_books.js') }}"></script> <script src="{{ url_for('static', filename='js/edit_books.js') }}"></script>
<script src="{{ url_for('static', filename='js/fullscreen.js') }}"></script> <script src="{{ url_for('static', filename='js/fullscreen.js') }}"></script>

View File

@ -4,7 +4,7 @@
{% if sort %}data-sortable="true" {% endif %} {% if sort %}data-sortable="true" {% endif %}
data-visible = "{{visiblility.get(parameter)}}" data-visible = "{{visiblility.get(parameter)}}"
data-escape="true" data-escape="true"
{% if g.user.role_edit() %} {% if current_user.role_edit() %}
data-editable-type="text" data-editable-type="text"
data-editable-url="{{ url_for('edit-book.edit_list_book', param=parameter)}}" data-editable-url="{{ url_for('edit-book.edit_list_book', param=parameter)}}"
data-editable-title="{{ edit_text }}" data-editable-title="{{ edit_text }}"
@ -53,10 +53,10 @@
</div> </div>
<table id="books-table" class="table table-no-bordered table-striped" <table id="books-table" class="table table-no-bordered table-striped"
data-url="{{url_for('web.list_books')}}" data-locale="{{ g.user.locale }}"> data-url="{{url_for('web.list_books')}}" data-locale="{{ current_user.locale }}">
<thead> <thead>
<tr> <tr>
{% if g.user.role_edit() %} {% if current_user.role_edit() %}
<th data-field="state" data-checkbox="true" data-sortable="true"></th> <th data-field="state" data-checkbox="true" data-sortable="true"></th>
{% endif %} {% endif %}
<th data-field="id" id="id" data-visible="false" data-switchable="false"></th> <th data-field="id" id="id" data-visible="false" data-switchable="false"></th>
@ -66,37 +66,37 @@
{{ text_table_row('authors', _('Enter Authors'),_('Authors'), true, true) }} {{ text_table_row('authors', _('Enter Authors'),_('Authors'), true, true) }}
{{ text_table_row('tags', _('Enter Categories'),_('Categories'), false, true) }} {{ text_table_row('tags', _('Enter Categories'),_('Categories'), false, true) }}
{{ text_table_row('series', _('Enter Series'),_('Series'), false, true) }} {{ text_table_row('series', _('Enter Series'),_('Series'), false, true) }}
<th data-field="series_index" id="series_index" data-visible="{{visiblility.get('series_index')}}" data-edit-validate="{{ _('This Field is Required') }}" data-sortable="true" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-min="0" data-editable-url="{{ url_for('edit-book.edit_list_book', param='series_index')}}" data-edit="true" data-editable-title="{{_('Enter Title')}}"{% endif %}>{{_('Series Index')}}</th> <th data-field="series_index" id="series_index" data-visible="{{visiblility.get('series_index')}}" data-edit-validate="{{ _('This Field is Required') }}" data-sortable="true" {% if current_user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-min="0" data-editable-url="{{ url_for('edit-book.edit_list_book', param='series_index')}}" data-edit="true" data-editable-title="{{_('Enter Title')}}"{% endif %}>{{_('Series Index')}}</th>
{{ text_table_row('languages', _('Enter Languages'),_('Languages'), false, true) }} {{ text_table_row('languages', _('Enter Languages'),_('Languages'), false, true) }}
<!--th data-field="pubdate" data-type="date" data-visible="{{visiblility.get('pubdate')}}" data-viewformat="dd.mm.yyyy" id="pubdate" data-sortable="true">{{_('Publishing Date')}}</th--> <!--th data-field="pubdate" data-type="date" data-visible="{{visiblility.get('pubdate')}}" data-viewformat="dd.mm.yyyy" id="pubdate" data-sortable="true">{{_('Publishing Date')}}</th-->
{{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false, true) }} {{ text_table_row('publishers', _('Enter Publishers'),_('Publishers'), false, true) }}
<th data-field="comments" id="comments" data-escape="true" data-editable-mode="popup" data-visible="{{visiblility.get('comments')}}" data-sortable="false" {% if g.user.role_edit() %} data-editable-type="wysihtml5" data-editable-url="{{ url_for('edit-book.edit_list_book', param='comments')}}" data-edit="true" data-editable-title="{{_('Enter comments')}}"{% endif %}>{{_('Comments')}}</th> <th data-field="comments" id="comments" data-escape="true" data-editable-mode="popup" data-visible="{{visiblility.get('comments')}}" data-sortable="false" {% if current_user.role_edit() %} data-editable-type="wysihtml5" data-editable-url="{{ url_for('edit-book.edit_list_book', param='comments')}}" data-edit="true" data-editable-title="{{_('Enter comments')}}"{% endif %}>{{_('Comments')}}</th>
{% if g.user.check_visibility(32768) %} {% if current_user.check_visibility(32768) %}
{{ book_checkbox_row('is_archived', _('Archive Status'), false)}} {{ book_checkbox_row('is_archived', _('Archive Status'), false)}}
{% endif %} {% endif %}
{{ book_checkbox_row('read_status', _('Read Status'), false)}} {{ book_checkbox_row('read_status', _('Read Status'), false)}}
{% for c in cc %} {% for c in cc %}
{% if c.datatype == "int" %} {% if c.datatype == "int" %}
<th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="1" data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th> <th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if current_user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="1" data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th>
{% elif c.datatype == "rating" %} {% elif c.datatype == "rating" %}
<th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-formatter="ratingFormatter" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.5" data-editable-step="1" data-editable-min="1" data-editable-max="5" data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th> <th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-formatter="ratingFormatter" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if current_user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.5" data-editable-step="1" data-editable-min="1" data-editable-max="5" data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th>
{% elif c.datatype == "float" %} {% elif c.datatype == "float" %}
<th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if g.user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th> <th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if current_user.role_edit() %} data-editable-type="number" data-editable-placeholder="1" data-editable-step="0.01" data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th>
{% elif c.datatype == "enumeration" %} {% elif c.datatype == "enumeration" %}
<th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if g.user.role_edit() %} data-editable-type="select" data-editable-source={{ url_for('edit-book.table_get_custom_enum', c_id=c.id) }} data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th> <th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if current_user.role_edit() %} data-editable-type="select" data-editable-source={{ url_for('edit-book.table_get_custom_enum', c_id=c.id) }} data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th>
{% elif c.datatype in ["datetime"] %} {% elif c.datatype in ["datetime"] %}
<!-- missing --> <!-- missing -->
{% elif c.datatype == "text" %} {% elif c.datatype == "text" %}
{{ text_table_row('custom_column_' + c.id|string, _('Enter ') + c.name, c.name, false, false) }} {{ text_table_row('custom_column_' + c.id|string, _('Enter ') + c.name, c.name, false, false) }}
{% elif c.datatype == "comments" %} {% elif c.datatype == "comments" %}
<th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-escape="true" data-editable-mode="popup" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if g.user.role_edit() %} data-editable-type="wysihtml5" data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th> <th data-field="custom_column_{{ c.id|string }}" id="custom_column_{{ c.id|string }}" data-escape="true" data-editable-mode="popup" data-visible="{{visiblility.get('custom_column_'+ c.id|string)}}" data-sortable="false" {% if current_user.role_edit() %} data-editable-type="wysihtml5" data-editable-url="{{ url_for('edit-book.edit_list_book', param='custom_column_'+ c.id|string)}}" data-edit="true" data-editable-title="{{_('Enter ') + c.name}}"{% endif %}>{{c.name}}</th>
{% elif c.datatype == "bool" %} {% elif c.datatype == "bool" %}
{{ book_checkbox_row('custom_column_' + c.id|string, c.name, false)}} {{ book_checkbox_row('custom_column_' + c.id|string, c.name, false)}}
{% else %} {% else %}
<!--{{ text_table_row('custom_column_' + c.id|string, _('Enter ') + c.name, c.name, false, false) }} --> <!--{{ text_table_row('custom_column_' + c.id|string, _('Enter ') + c.name, c.name, false, false) }} -->
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if g.user.role_delete_books() and g.user.role_edit()%} {% if current_user.role_delete_books() and current_user.role_edit()%}
<th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th> <th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th>
{% endif %} {% endif %}
</tr> </tr>
@ -104,8 +104,8 @@
</table> </table>
{% endblock %} {% endblock %}
{% block modal %} {% block modal %}
{{ delete_book() }} {{ delete_book(current_user.role_delete_books()) }}
{% if g.user.role_edit() %} {% if current_user.role_edit() %}
<div class="modal fade" id="mergeModal" role="dialog" aria-labelledby="metaMergeLabel"> <div class="modal fade" id="mergeModal" role="dialog" aria-labelledby="metaMergeLabel">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -137,8 +137,8 @@
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-locale-all.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-locale-all.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
{% if not g.user.locale == 'en' %} {% if not current_user.locale == 'en' %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/locale/bootstrap-table-' + g.user.locale + '.min.js') }}" charset="UTF-8"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/locale/bootstrap-table-' + current_user.locale + '.min.js') }}" charset="UTF-8"></script>
{% endif %} {% endif %}
<script src="{{ url_for('static', filename='js/libs/wysihtml5-0.3.0.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/wysihtml5-0.3.0.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-wysihtml5-0.0.3.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-wysihtml5-0.0.3.min.js') }}"></script>

View File

@ -11,7 +11,7 @@
<div class="col-sm-9 col-lg-9 book-meta"> <div class="col-sm-9 col-lg-9 book-meta">
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Download, send to eReader, reading"> <div class="btn-group" role="group" aria-label="Download, send to eReader, reading">
{% if g.user.role_download() %} {% if current_user.role_download() %}
{% if entry.data|length %} {% if entry.data|length %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
{% if entry.data|length < 2 %} {% if entry.data|length < 2 %}
@ -37,7 +37,7 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if g.user.kindle_mail and entry.email_share_list %} {% if current_user.kindle_mail and entry.email_share_list %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if entry.email_share_list.__len__() == 1 %} {% if entry.email_share_list.__len__() == 1 %}
<div id="sendbtn" data-action="{{url_for('web.send_to_ereader', book_id=entry.id, book_format=entry.email_share_list[0]['format'], convert=entry.email_share_list[0]['convert'])}}" data-text="{{_('Send to eReader')}}" class="btn btn-primary postAction" role="button"><span class="glyphicon glyphicon-send"></span> {{entry.email_share_list[0]['text']}}</div> <div id="sendbtn" data-action="{{url_for('web.send_to_ereader', book_id=entry.id, book_format=entry.email_share_list[0]['format'], convert=entry.email_share_list[0]['convert'])}}" data-text="{{_('Send to eReader')}}" class="btn btn-primary postAction" role="button"><span class="glyphicon glyphicon-send"></span> {{entry.email_share_list[0]['text']}}</div>
@ -55,7 +55,7 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if entry.reader_list and g.user.role_viewer() %} {% if entry.reader_list and current_user.role_viewer() %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
{% if entry.reader_list|length > 1 %} {% if entry.reader_list|length > 1 %}
<button id="read-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button id="read-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -72,7 +72,7 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
{% if entry.audio_entries|length > 0 and g.user.role_viewer() %} {% if entry.audio_entries|length > 0 and current_user.role_viewer() %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
{% if entry.audio_entries|length > 1 %} {% if entry.audio_entries|length > 1 %}
<button id="listen-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button id="listen-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -213,7 +213,7 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if not g.user.is_anonymous %} {% if not current_user.is_anonymous %}
<div class="custom_columns"> <div class="custom_columns">
<p> <p>
@ -225,7 +225,7 @@
</label> </label>
</form> </form>
</p> </p>
{% if g.user.check_visibility(32768) %} {% if current_user.check_visibility(32768) %}
<p> <p>
<form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST"> <form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
@ -250,8 +250,8 @@
<div class="more-stuff"> <div class="more-stuff">
{% if g.user.is_authenticated %} {% if current_user.is_authenticated %}
{% if g.user.shelf.all() or g.shelves_access %} {% if current_user.shelf.all() or g.shelves_access %}
<div id="shelf-actions" class="btn-toolbar" role="toolbar"> <div id="shelf-actions" class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Add to shelves"> <div class="btn-group" role="group" aria-label="Add to shelves">
<button id="add-to-shelf" type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button id="add-to-shelf" type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -260,7 +260,7 @@
</button> </button>
<ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf"> <ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf">
{% for shelf in g.shelves_access %} {% for shelf in g.shelves_access %}
{% if not shelf.id in books_shelfs and ( not shelf.is_public or g.user.role_edit_shelfs() ) %} {% if not shelf.id in books_shelfs and ( not shelf.is_public or current_user.role_edit_shelfs() ) %}
<li> <li>
<a data-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}" <a data-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
data-remove-href="{{ url_for('shelf.remove_from_shelf', book_id=entry.id, shelf_id=shelf.id) }}" data-remove-href="{{ url_for('shelf.remove_from_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
@ -281,7 +281,7 @@
data-add-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}" data-add-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
class="btn btn-sm btn-default" role="button" data-shelf-action="remove" class="btn btn-sm btn-default" role="button" data-shelf-action="remove"
> >
<span {% if not shelf.is_public or g.user.role_edit_shelfs() %} <span {% if not shelf.is_public or current_user.role_edit_shelfs() %}
class="glyphicon glyphicon-remove" class="glyphicon glyphicon-remove"
{% endif %}></span> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %} {% endif %}></span> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}
</a> </a>
@ -294,7 +294,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if g.user.role_edit() %} {% if current_user.role_edit() %}
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Edit/Delete book"> <div class="btn-group" role="group" aria-label="Edit/Delete book">
<a href="{{ url_for('edit-book.show_edit_book', book_id=entry.id) }}" class="btn btn-sm btn-primary" id="edit_book" role="button"><span class="glyphicon glyphicon-edit"></span> {{_('Edit Metadata')}}</a> <a href="{{ url_for('edit-book.show_edit_book', book_id=entry.id) }}" class="btn btn-sm btn-primary" id="edit_book" role="button"><span class="glyphicon glyphicon-edit"></span> {{_('Edit Metadata')}}</a>

View File

@ -80,7 +80,7 @@
</div> </div>
<button id="domain_allow_submit" class="btn btn-default">{{_('Add')}}</button> <button id="domain_allow_submit" class="btn btn-default">{{_('Add')}}</button>
</form> </form>
<table class="table table-no-bordered" id="domain-allow-table" data-url="{{url_for('admin.list_domain', allow=1)}}" data-id-field="id" data-show-header="false" data-editable-mode="inline" data-locale="{{ g.user.locale }}"> <table class="table table-no-bordered" id="domain-allow-table" data-url="{{url_for('admin.list_domain', allow=1)}}" data-id-field="id" data-show-header="false" data-editable-mode="inline" data-locale="{{ current_user.locale }}">
<thead> <thead>
<tr> <tr>
<th data-field="domain" id="domain-allow" data-escape="true" data-editable-type="text" data-editable-url="{{ url_for('admin.edit_domain', allow = 1)}}" data-editable="true" data-editable-title="{{_('Enter domainname')}}"></th> <th data-field="domain" id="domain-allow" data-escape="true" data-editable-type="text" data-editable-url="{{ url_for('admin.edit_domain', allow = 1)}}" data-editable="true" data-editable-title="{{_('Enter domainname')}}"></th>
@ -90,7 +90,7 @@
</thead> </thead>
</table> </table>
<h2>{{_('Denied Domains (Blacklist)')}}</h2> <h2>{{_('Denied Domains (Blacklist)')}}</h2>
<table class="table table-no-bordered" id="domain-deny-table" data-url="{{url_for('admin.list_domain', allow=0)}}" data-id-field="id" data-show-header="false" data-editable-mode="inline" data-locale="{{ g.user.locale }}"> <table class="table table-no-bordered" id="domain-deny-table" data-url="{{url_for('admin.list_domain', allow=0)}}" data-id-field="id" data-show-header="false" data-editable-mode="inline" data-locale="{{ current_user.locale }}">
<thead> <thead>
<tr> <tr>
<th data-field="domain" id="domain-deny" data-escape="true" data-editable-type="text" data-editable-url="{{ url_for('admin.edit_domain', allow = 0)}}" data-editable="true" data-editable-title="{{_('Enter domainname')}}"></th> <th data-field="domain" id="domain-deny" data-escape="true" data-editable-type="text" data-editable-url="{{ url_for('admin.edit_domain', allow = 0)}}" data-editable="true" data-editable-title="{{_('Enter domainname')}}"></th>

View File

@ -1,7 +1,7 @@
{% import 'image.html' as image %} {% import 'image.html' as image %}
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
{% if g.user.show_detail_random() and page != "discover" %} {% if current_user.show_detail_random() and page != "discover" %}
<div class="discover random-books"> <div class="discover random-books">
<h2 class="random-books">{{_('Discover (Random Books)')}}</h2> <h2 class="random-books">{{_('Discover (Random Books)')}}</h2>
<div class="row display-flex"> <div class="row display-flex">

View File

@ -1,7 +1,7 @@
{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal, delete_confirm_modal, change_confirm_modal %} {% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal, delete_confirm_modal, change_confirm_modal %}
{% import 'image.html' as image %} {% import 'image.html' as image %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ g.user.locale }}"> <html lang="{{ current_user.locale }}">
<head> <head>
<title>{{instance}} | {{title}}</title> <title>{{instance}} | {{title}}</title>
<meta charset="utf-8"> <meta charset="utf-8">
@ -40,8 +40,9 @@
<div class="home-btn"><a class="home-btn-tooltip" href="/" data-toggle="tooltip" title="" data-placement="bottom" data-original-title="Home"></a></div> <div class="home-btn"><a class="home-btn-tooltip" href="/" data-toggle="tooltip" title="" data-placement="bottom" data-original-title="Home"></a></div>
<div class="plexBack"><a href="{{url_for('web.index')}}"></a></div> <div class="plexBack"><a href="{{url_for('web.index')}}"></a></div>
{% endif %} {% endif %}
{% if g.user.is_authenticated or g.allow_anonymous %} {% if current_user.is_authenticated or g.allow_anonymous %}
<form class="navbar-form navbar-left" role="search" action="{{url_for('search.simple_search')}}" method="GET"> <form class="navbar-form navbar-left" role="search" action="{{url_for('search.simple_search')}}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group input-group input-group-sm"> <div class="form-group input-group input-group-sm">
<label for="query" class="sr-only">{{_('Search')}}</label> <label for="query" class="sr-only">{{_('Search')}}</label>
<input type="text" class="form-control" id="query" name="query" placeholder="{{_('Search Library')}}" value="{{searchterm}}"> <input type="text" class="form-control" id="query" name="query" placeholder="{{_('Search Library')}}" value="{{searchterm}}">
@ -52,28 +53,28 @@
</form> </form>
{% endif %} {% endif %}
<div class="navbar-collapse collapse"> <div class="navbar-collapse collapse">
{% if g.user.is_authenticated or g.allow_anonymous %} {% if current_user.is_authenticated or g.allow_anonymous %}
<ul class="nav navbar-nav "> <ul class="nav navbar-nav ">
<li><a href="{{url_for('search.advanced_search')}}" id="advanced_search"><span class="glyphicon glyphicon-search"></span><span class="hidden-sm"> {{_('Advanced Search')}}</span></a></li> <li><a href="{{url_for('search.advanced_search')}}" id="advanced_search"><span class="glyphicon glyphicon-search"></span><span class="hidden-sm"> {{_('Advanced Search')}}</span></a></li>
</ul> </ul>
{% endif %} {% endif %}
<ul class="nav navbar-nav navbar-right" id="main-nav"> <ul class="nav navbar-nav navbar-right" id="main-nav">
{% if g.user.is_authenticated or g.allow_anonymous %} {% if current_user.is_authenticated or g.allow_anonymous %}
{% if g.current_theme == 1 %} {% if g.current_theme == 1 %}
<li class="dropdown"><a href="#" class="dropdown-toggle profileDrop" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-user"></span></a> <li class="dropdown"><a href="#" class="dropdown-toggle profileDrop" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-user"></span></a>
<ul class="dropdown-menu profileDropli"> <ul class="dropdown-menu profileDropli">
<li><a id="top_user" data-text="{{_('Account')}}" href="{{url_for('web.profile')}}"><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{g.user.name}}</span></a></li> <li><a id="top_user" data-text="{{_('Account')}}" href="{{url_for('web.profile')}}"><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{current_user.name}}</span></a></li>
{% if g.allow_registration and not g.user.is_authenticated %} {% if g.allow_registration and not current_user.is_authenticated %}
<li><a id="login" href="{{url_for('web.login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li> <li><a id="login" href="{{url_for('web.login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li>
<li><a id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li> <li><a id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li>
{% endif %} {% endif %}
{% if not g.user.is_anonymous %} {% if not current_user.is_anonymous %}
<li><a id="logout" href="{{url_for('web.logout')}}"><span class="glyphicon glyphicon-log-out"></span> <span class="hidden-sm">{{_('Logout')}}</span></a></li> <li><a id="logout" href="{{url_for('web.logout')}}"><span class="glyphicon glyphicon-log-out"></span> <span class="hidden-sm">{{_('Logout')}}</span></a></li>
{% endif %} {% endif %}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}
{% if g.user.role_upload() and g.allow_upload %} {% if current_user.role_upload() and g.allow_upload %}
<li> <li>
<form id="form-upload" class="navbar-form" action="{{ url_for('edit-book.upload') }}" data-title="{{_('Uploading...')}}" data-footer="{{_('Close')}}" data-failed="{{_('Error')}}" data-message="{{_('Upload done, processing, please wait...')}}" method="post" enctype="multipart/form-data"> <form id="form-upload" class="navbar-form" action="{{ url_for('edit-book.upload') }}" data-title="{{_('Uploading...')}}" data-footer="{{_('Close')}}" data-failed="{{_('Error')}}" data-message="{{_('Upload done, processing, please wait...')}}" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
@ -84,20 +85,20 @@
</form> </form>
</li> </li>
{% endif %} {% endif %}
{% if not g.user.is_anonymous and not simple%} {% if not current_user.is_anonymous and not simple%}
<li class="top_tasks"><a id="top_tasks" href="{{url_for('tasks.get_tasks_status')}}"><span class="glyphicon glyphicon-tasks"></span> <span class="hidden-sm">{{_('Tasks')}}</span></a></li> <li class="top_tasks"><a id="top_tasks" href="{{url_for('tasks.get_tasks_status')}}"><span class="glyphicon glyphicon-tasks"></span> <span class="hidden-sm">{{_('Tasks')}}</span></a></li>
{% endif %} {% endif %}
{% if g.user.role_admin() %} {% if current_user.role_admin() %}
<li><a id="top_admin" data-text="{{_('Settings')}}" href="{{url_for('admin.admin')}}"><span class="glyphicon glyphicon-dashboard"></span> <span class="hidden-sm">{{_('Admin')}}</span></a></li> <li><a id="top_admin" data-text="{{_('Settings')}}" href="{{url_for('admin.admin')}}"><span class="glyphicon glyphicon-dashboard"></span> <span class="hidden-sm">{{_('Admin')}}</span></a></li>
{% endif %} {% endif %}
{% if g.current_theme == 0 %} {% if g.current_theme == 0 %}
<li><a id="top_user" data-text="{{_('Account')}}" href="{{url_for('web.profile')}}"><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{g.user.name}}</span></a></li> <li><a id="top_user" data-text="{{_('Account')}}" href="{{url_for('web.profile')}}"><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{current_user.name}}</span></a></li>
{% if not g.user.is_anonymous %} {% if not current_user.is_anonymous %}
<li><a id="logout" href="{{url_for('web.logout')}}"><span class="glyphicon glyphicon-log-out"></span> <span class="hidden-sm">{{_('Logout')}}</span></a></li> <li><a id="logout" href="{{url_for('web.logout')}}"><span class="glyphicon glyphicon-log-out"></span> <span class="hidden-sm">{{_('Logout')}}</span></a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if g.allow_registration and not g.user.is_authenticated and g.current_theme == 0 %} {% if g.allow_registration and not current_user.is_authenticated and g.current_theme == 0 %}
<li><a id="login" href="{{url_for('web.login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li> <li><a id="login" href="{{url_for('web.login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li>
<li><a id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li> <li><a id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li>
{% endif %} {% endif %}
@ -138,22 +139,22 @@
{%endif%} {%endif%}
<div class="container-fluid"> <div class="container-fluid">
<div class="row-fluid"> <div class="row-fluid">
{% if g.user.is_authenticated or g.allow_anonymous %} {% if current_user.is_authenticated or g.allow_anonymous %}
<div class="col-sm-2"> <div class="col-sm-2">
<nav class="navigation"> <nav class="navigation">
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav"> <ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
<li class="nav-head hidden-xs">{{_('Browse')}}</li> <li class="nav-head hidden-xs">{{_('Browse')}}</li>
{% for element in sidebar %} {% for element in sidebar %}
{% if g.user.check_visibility(element['visibility']) and element['public'] %} {% if current_user.check_visibility(element['visibility']) and element['public'] %}
<li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'], data=element['page'], sort_param='stored')}}"><span class="glyphicon {{element['glyph']}}"></span> {{_(element['text'])}}</a></li> <li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'], data=element['page'], sort_param='stored')}}"><span class="glyphicon {{element['glyph']}}"></span> {{_(element['text'])}}</a></li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if g.user.is_authenticated or g.allow_anonymous %} {% if current_user.is_authenticated or g.allow_anonymous %}
<li class="nav-head hidden-xs public-shelves">{{_('Shelves')}}</li> <li class="nav-head hidden-xs public-shelves">{{_('Shelves')}}</li>
{% for shelf in g.shelves_access %} {% for shelf in g.shelves_access %}
<li><a href="{{url_for('shelf.show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list shelf"></span> {{shelf.name|shortentitle(40)}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li> <li><a href="{{url_for('shelf.show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list shelf"></span> {{shelf.name|shortentitle(40)}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li>
{% endfor %} {% endfor %}
{% if not g.user.is_anonymous %} {% if not current_user.is_anonymous %}
<li id="nav_createshelf" class="create-shelf"><a href="{{url_for('shelf.create_shelf')}}">{{_('Create a Shelf')}}</a></li> <li id="nav_createshelf" class="create-shelf"><a href="{{url_for('shelf.create_shelf')}}">{{_('Create a Shelf')}}</a></li>
<li id="nav_about" {% if page == 'stat' %}class="active"{% endif %}><a href="{{url_for('about.stats')}}"><span class="glyphicon glyphicon-info-sign"></span> {{_('About')}}</a></li> <li id="nav_about" {% if page == 'stat' %}class="active"{% endif %}><a href="{{url_for('about.stats')}}"><span class="glyphicon glyphicon-info-sign"></span> {{_('About')}}</a></li>
{% endif %} {% endif %}

View File

@ -149,7 +149,7 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if not g.user.is_anonymous %} {% if not current_user.is_anonymous %}
<div class="custom_columns"> <div class="custom_columns">
<p> <p>
@ -159,7 +159,7 @@
<span>{{_('Read')}}</span> <span>{{_('Read')}}</span>
</label> </label>
</p> </p>
{% if g.user.check_visibility(32768) %} {% if current_user.check_visibility(32768) %}
<p> <p>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label"> <label class="block-label">
@ -182,8 +182,8 @@
<div class="more-stuff"> <div class="more-stuff">
{% if g.user.is_authenticated %} {% if current_user.is_authenticated %}
{% if g.user.shelf.all() or g.shelves_access %} {% if current_user.shelf.all() or g.shelves_access %}
<div id="shelf-actions" class="btn-toolbar" role="toolbar"> <div id="shelf-actions" class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Add to shelves"> <div class="btn-group" role="group" aria-label="Add to shelves">
<button id="add-to-shelf" type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button id="add-to-shelf" type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -192,7 +192,7 @@
</button> </button>
<ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf"> <ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf">
{% for shelf in g.shelves_access %} {% for shelf in g.shelves_access %}
{% if not shelf.id in books_shelfs and ( not shelf.is_public or g.user.role_edit_shelfs() ) %} {% if not shelf.id in books_shelfs and ( not shelf.is_public or current_user.role_edit_shelfs() ) %}
<li> <li>
<a data-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}" <a data-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
data-remove-href="{{ url_for('shelf.remove_from_shelf', book_id=entry.id, shelf_id=shelf.id) }}" data-remove-href="{{ url_for('shelf.remove_from_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
@ -213,7 +213,7 @@
data-add-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}" data-add-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
class="btn btn-sm btn-default" role="button" data-shelf-action="remove" class="btn btn-sm btn-default" role="button" data-shelf-action="remove"
> >
<span {% if not shelf.is_public or g.user.role_edit_shelfs() %} <span {% if not shelf.is_public or current_user.role_edit_shelfs() %}
class="glyphicon glyphicon-remove" class="glyphicon glyphicon-remove"
{% endif %}></span> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %} {% endif %}></span> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}
</a> </a>
@ -343,7 +343,7 @@ window.calibre = {
bookUrl: "{{ url_for('static', filename=mp3file) }}/", bookUrl: "{{ url_for('static', filename=mp3file) }}/",
bookmarkUrl: "{{ url_for('web.set_bookmark', book_id=mp3file, book_format=audioformat.upper()) }}", bookmarkUrl: "{{ url_for('web.set_bookmark', book_id=mp3file, book_format=audioformat.upper()) }}",
bookmark: "{{ bookmark.bookmark_key if bookmark != None }}", bookmark: "{{ bookmark.bookmark_key if bookmark != None }}",
useBookmarks: "{{ g.user.is_authenticated | tojson }}" useBookmarks: "{{ current_user.is_authenticated | tojson }}"
}; };
</script> </script>

View File

@ -37,8 +37,8 @@
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro delete_book() %} {% macro delete_book(allow) %}
{% if g.user.role_delete_books() %} {% if allow %}
<div class="modal fade" id="deleteModal" role="dialog" aria-labelledby="metaDeleteLabel"> <div class="modal fade" id="deleteModal" role="dialog" aria-labelledby="metaDeleteLabel">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

View File

@ -108,7 +108,7 @@
bookmarkUrl: "{{ url_for('web.set_bookmark', book_id=bookid, book_format='EPUB') }}", bookmarkUrl: "{{ url_for('web.set_bookmark', book_id=bookid, book_format='EPUB') }}",
bookUrl: "{{ url_for('web.serve_book', book_id=bookid, book_format='epub', anyname='file.epub') }}", bookUrl: "{{ url_for('web.serve_book', book_id=bookid, book_format='epub', anyname='file.epub') }}",
bookmark: "{{ bookmark.bookmark_key if bookmark != None }}", bookmark: "{{ bookmark.bookmark_key if bookmark != None }}",
useBookmarks: "{{ g.user.is_authenticated | tojson }}" useBookmarks: "{{ current_user.is_authenticated | tojson }}"
}; };
function selectTheme(id) { function selectTheme(id) {

View File

@ -171,7 +171,7 @@ See https://github.com/adobe-type-tools/cmap-resources
<span data-l10n-id="presentation_mode_label">Presentation Mode</span> <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
</button> </button>
{% if g.user.role_download() %} {% if current_user.role_download() %}
<button id="secondaryPrint" class="secondaryToolbarButton visibleMediumView" title="Print" tabindex="53" data-l10n-id="print"> <button id="secondaryPrint" class="secondaryToolbarButton visibleMediumView" title="Print" tabindex="53" data-l10n-id="print">
<span data-l10n-id="print_label">Print</span> <span data-l10n-id="print_label">Print</span>
</button> </button>
@ -280,7 +280,7 @@ See https://github.com/adobe-type-tools/cmap-resources
<span data-l10n-id="presentation_mode_label">Presentation Mode</span> <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
</button> </button>
{% if g.user.role_download() %} {% if current_user.role_download() %}
<button id="print" class="toolbarButton hiddenMediumView" title="Print" tabindex="33" data-l10n-id="print"> <button id="print" class="toolbarButton hiddenMediumView" title="Print" tabindex="33" data-l10n-id="print">
<span data-l10n-id="print_label">Print</span> <span data-l10n-id="print_label">Print</span>
</button> </button>

View File

@ -7,8 +7,8 @@
<p>{{_('Search Term:')}} {{adv_searchterm}}</p> <p>{{_('Search Term:')}} {{adv_searchterm}}</p>
{% else %} {% else %}
<h2>{{result_count}} {{_('Results for:')}} {{adv_searchterm}}</h2> <h2>{{result_count}} {{_('Results for:')}} {{adv_searchterm}}</h2>
{% if g.user.is_authenticated %} {% if current_user.is_authenticated %}
{% if g.user.shelf.all() or g.shelves_access %} {% if current_user.shelf.all() or g.shelves_access %}
<div id="shelf-actions" class="btn-toolbar" role="toolbar"> <div id="shelf-actions" class="btn-toolbar" role="toolbar">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="btn-group" role="group" aria-label="Add to shelves"> <div class="btn-group" role="group" aria-label="Add to shelves">
@ -18,7 +18,7 @@
</button> </button>
<ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf"> <ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf">
{% for shelf in g.shelves_access %} {% for shelf in g.shelves_access %}
{% if not shelf.is_public or g.user.role_edit_shelfs() %} {% if not shelf.is_public or current_user.role_edit_shelfs() %}
<li><a class="postAction" role="button" data-action="{{ url_for('shelf.search_to_shelf', shelf_id=shelf.id) }}"> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li> <li><a class="postAction" role="button" data-action="{{ url_for('shelf.search_to_shelf', shelf_id=shelf.id) }}"> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li>
{% endif %} {% endif %}
{%endfor%} {%endfor%}

View File

@ -230,18 +230,18 @@
{% block js %} {% block js %}
<script> <script>
var language = '{{ g.user.locale }}'; var language = '{{ current_user.locale }}';
</script> </script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/bootstrap-datepicker.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/bootstrap-datepicker.min.js') }}"></script>
{% if not g.user.locale == 'en' %} {% if not current_user.locale == 'en' %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + g.user.locale + '.min.js') }}" charset="UTF-8"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + current_user.locale + '.min.js') }}" charset="UTF-8"></script>
{% endif %} {% endif %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-rating-input.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-rating-input.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/typeahead.bundle.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/typeahead.bundle.js') }}"></script>
<script src="{{ url_for('static', filename='js/edit_books.js') }}"></script> <script src="{{ url_for('static', filename='js/edit_books.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script>
{% if not g.user.locale == 'en' %} {% if not current_user.locale == 'en' %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-select/defaults-' + g.user.locale + '.min.js') }}" charset="UTF-8"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-select/defaults-' + current_user.locale + '.min.js') }}" charset="UTF-8"></script>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block header %} {% block header %}

View File

@ -4,11 +4,11 @@
<div class="discover"> <div class="discover">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<!--form method="post"---> <!--form method="post"--->
{% if g.user.role_download() %} {% if current_user.role_download() %}
<a id="shelf_down" href="{{ url_for('shelf.show_simpleshelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Download') }} </a> <a id="shelf_down" href="{{ url_for('shelf.show_simpleshelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Download') }} </a>
{% endif %} {% endif %}
{% if g.user.is_authenticated %} {% if current_user.is_authenticated %}
{% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %} {% if (current_user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="btn btn-danger" data-action="{{url_for('shelf.delete_shelf', shelf_id=shelf.id)}}" id="delete_shelf" data-value="{{ shelf.id }}">{{ _('Delete this Shelf') }}</div> <div class="btn btn-danger" data-action="{{url_for('shelf.delete_shelf', shelf_id=shelf.id)}}" id="delete_shelf" data-value="{{ shelf.id }}">{{ _('Delete this Shelf') }}</div>
<a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf Properties') }} </a> <a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf Properties') }} </a>

View File

@ -8,7 +8,7 @@
<label for="title">{{_('Title')}}</label> <label for="title">{{_('Title')}}</label>
<input type="text" class="form-control" name="title" id="title" value="{{ shelf.name if shelf.name != None }}"> <input type="text" class="form-control" name="title" id="title" value="{{ shelf.name if shelf.name != None }}">
</div> </div>
{% if g.user.role_edit_shelfs() %} {% if current_user.role_edit_shelfs() %}
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" name="is_public" {% if shelf.is_public == 1 %}checked{% endif %}> {{_('Share with Everyone')}} <input type="checkbox" name="is_public" {% if shelf.is_public == 1 %}checked{% endif %}> {{_('Share with Everyone')}}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ g.user.locale }}"> <html lang="{{ current_user.locale }}">
<head> <head>
<title>{{instance}} | {{title}}</title> <title>{{instance}} | {{title}}</title>
<meta charset="utf-8"> <meta charset="utf-8">
@ -58,7 +58,7 @@
</div> </div>
<div class="btn-group" role="group" aria-label="Download, send to eReader, reading"> <div class="btn-group" role="group" aria-label="Download, send to eReader, reading">
{% if g.user.role_download() %} {% if current_user.role_download() %}
{% if entry.Books.data|length %} {% if entry.Books.data|length %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
{% for format in entry.Books.data %} {% for format in entry.Books.data %}

View File

@ -25,7 +25,7 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
{% if g.user.role_admin() %} {% if current_user.role_admin() %}
<h3>{{_('System Statistics')}}</h3> <h3>{{_('System Statistics')}}</h3>
<table id="libs" class="table"> <table id="libs" class="table">
<thead> <thead>

View File

@ -5,10 +5,10 @@
{% block body %} {% block body %}
<div class="discover"> <div class="discover">
<h2>{{_('Tasks')}}</h2> <h2>{{_('Tasks')}}</h2>
<table class="table table-no-bordered" id="tasktable" data-url="{{ url_for('tasks.get_email_status_json') }}" data-sort-name="starttime" data-sort-order="asc" data-locale="{{ g.user.locale }}"> <table class="table table-no-bordered" id="tasktable" data-url="{{ url_for('tasks.get_email_status_json') }}" data-sort-name="starttime" data-sort-order="asc" data-locale="{{ current_user.locale }}">
<thead> <thead>
<tr> <tr>
{% if g.user.role_admin() %} {% if current_user.role_admin() %}
<th data-halign="right" data-align="right" data-field="user" data-sortable="true">{{_('User')}}</th> <th data-halign="right" data-align="right" data-field="user" data-sortable="true">{{_('User')}}</th>
{% endif %} {% endif %}
<th data-halign="right" data-align="right" data-field="taskMessage" data-sortable="true">{{_('Task')}}</th> <th data-halign="right" data-align="right" data-field="taskMessage" data-sortable="true">{{_('Task')}}</th>
@ -16,7 +16,7 @@
<th data-halign="right" data-align="right" data-field="progress" data-sortable="true" data-sorter="elementSorter">{{_('Progress')}}</th> <th data-halign="right" data-align="right" data-field="progress" data-sortable="true" data-sorter="elementSorter">{{_('Progress')}}</th>
<th data-halign="right" data-align="right" data-field="runtime" data-sortable="true" data-sort-name="rt">{{_('Run Time')}}</th> <th data-halign="right" data-align="right" data-field="runtime" data-sortable="true" data-sort-name="rt">{{_('Run Time')}}</th>
<th data-halign="right" data-align="right" data-field="starttime" data-sortable="true" data-sort-name="id">{{_('Start Time')}}</th> <th data-halign="right" data-align="right" data-field="starttime" data-sortable="true" data-sort-name="id">{{_('Start Time')}}</th>
{% if g.user.role_admin() %} {% if current_user.role_admin() %}
<th data-halign="right" data-align="right" data-formatter="TaskActions" data-switchable="false">{{_('Actions')}}</th> <th data-halign="right" data-align="right" data-formatter="TaskActions" data-switchable="false">{{_('Actions')}}</th>
{% endif %} {% endif %}
<th data-field="id" data-visible="false"></th> <th data-field="id" data-visible="false"></th>
@ -27,8 +27,8 @@
</div> </div>
{% endblock %} {% endblock %}
{% block modal %} {% block modal %}
{{ delete_book() }} {{ delete_book(current_user.role_delete_books()) }}
{% if g.user.role_admin() %} {% if current_user.role_admin() %}
<div class="modal fade" id="cancelTaskModal" role="dialog" aria-labelledby="metaCancelTaskLabel"> <div class="modal fade" id="cancelTaskModal" role="dialog" aria-labelledby="metaCancelTaskLabel">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

View File

@ -5,7 +5,7 @@
<form role="form" method="POST" autocomplete="off"> <form role="form" method="POST" autocomplete="off">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-md-10 col-lg-8"> <div class="col-md-10 col-lg-8">
{% if new_user or ( g.user and content.name != "Guest" and g.user.role_admin() ) %} {% if new_user or ( current_user and content.name != "Guest" and current_user.role_admin() ) %}
<div class="form-group required"> <div class="form-group required">
<label for="name">{{_('Username')}}</label> <label for="name">{{_('Username')}}</label>
<input type="text" class="form-control" name="name" id="name" value="{{ content.name if content.name != None }}" autocomplete="off"> <input type="text" class="form-control" name="name" id="name" value="{{ content.name if content.name != None }}" autocomplete="off">
@ -15,8 +15,8 @@
<label for="email">{{_('Email')}}</label> <label for="email">{{_('Email')}}</label>
<input type="email" class="form-control" name="email" id="email" value="{{ content.email if content.email != None }}" autocomplete="off"> <input type="email" class="form-control" name="email" id="email" value="{{ content.email if content.email != None }}" autocomplete="off">
</div> </div>
{% if ( g.user and g.user.role_passwd() or g.user.role_admin() ) and not content.role_anonymous() %} {% if ( current_user and current_user.role_passwd() or current_user.role_admin() ) and not content.role_anonymous() %}
{% if g.user and g.user.role_admin() and not new_user and not profile and ( mail_configured and content.email if content.email != None ) %} {% if current_user and current_user.role_admin() and not new_user and not profile and ( mail_configured and content.email if content.email != None ) %}
<a class="btn btn-default postAction" id="resend_password" role="button" data-action="{{url_for('admin.reset_user_password', user_id = content.id) }}">{{_('Reset user Password')}}</a> <a class="btn btn-default postAction" id="resend_password" role="button" data-action="{{url_for('admin.reset_user_password', user_id = content.id) }}">{{_('Reset user Password')}}</a>
{% endif %} {% endif %}
<div class="form-group"> <div class="form-group">
@ -83,13 +83,13 @@
<input type="checkbox" name="Show_detail_random" id="Show_detail_random" {% if content.show_detail_random() %}checked{% endif %}> <input type="checkbox" name="Show_detail_random" id="Show_detail_random" {% if content.show_detail_random() %}checked{% endif %}>
<label for="Show_detail_random">{{_('Show Random Books in Detail View')}}</label> <label for="Show_detail_random">{{_('Show Random Books in Detail View')}}</label>
</div> </div>
{% if ( g.user and g.user.role_admin() and not new_user ) and not simple %} {% if ( current_user and current_user.role_admin() and not new_user ) and not simple %}
<a href="#" id="get_user_tags" class="btn btn-default" data-id="{{content.id}}" data-toggle="modal" data-target="#restrictModal">{{_('Add Allowed/Denied Tags')}}</a> <a href="#" id="get_user_tags" class="btn btn-default" data-id="{{content.id}}" data-toggle="modal" data-target="#restrictModal">{{_('Add Allowed/Denied Tags')}}</a>
<a href="#" id="get_user_column_values" data-id="{{content.id}}" class="btn btn-default" data-toggle="modal" data-target="#restrictModal">{{_('Add allowed/Denied Custom Column Values')}}</a> <a href="#" id="get_user_column_values" data-id="{{content.id}}" class="btn btn-default" data-toggle="modal" data-target="#restrictModal">{{_('Add allowed/Denied Custom Column Values')}}</a>
{% endif %} {% endif %}
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
{% if g.user and g.user.role_admin() and not profile %} {% if current_user and current_user.role_admin() and not profile %}
{% if not content.role_anonymous() %} {% if not content.role_anonymous() %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}> <input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}>
@ -143,7 +143,7 @@
{% if not profile %} {% if not profile %}
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div> <div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
{% endif %} {% endif %}
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %} {% if current_user and current_user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
<div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div> <div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>
{% endif %} {% endif %}
</div> </div>

View File

@ -125,7 +125,7 @@
</div> </div>
</div> </div>
<table id="user-table" class="table table-no-bordered table-striped" <table id="user-table" class="table table-no-bordered table-striped"
data-url="{{url_for('admin.list_users')}}" data-locale="{{ g.user.locale }}"> data-url="{{url_for('admin.list_users')}}" data-locale="{{ current_user.locale }}">
<thead> <thead>
<tr> <tr>
<th data-name="edit" data-buttontext="{{_('Edit User')}}" data-visible="{{visiblility.get('edit')}}" data-formatter="singleUserFormatter">{{_('Edit')}}</th> <th data-name="edit" data-buttontext="{{_('Edit User')}}" data-visible="{{visiblility.get('edit')}}" data-formatter="singleUserFormatter">{{_('Edit')}}</th>
@ -185,8 +185,8 @@
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script>
{% if not g.user.locale == 'en' %} {% if not current_user.locale == 'en' %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-select/defaults-' + g.user.locale + '.min.js') }}" charset="UTF-8"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-select/defaults-' + current_user.locale + '.min.js') }}" charset="UTF-8"></script>
{% endif %} {% endif %}
<script src="{{ url_for('static', filename='js/table.js') }}"></script> <script src="{{ url_for('static', filename='js/table.js') }}"></script>
{% endblock %} {% endblock %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -16,16 +16,16 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import binascii
from functools import wraps from functools import wraps
from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import func
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from flask_login import login_required, login_user from flask_login import login_required, login_user
from flask import request, Response
from . import lm, ub, config, constants, services from . import lm, ub, config, constants, services, logger
log = logger.create()
def login_required_if_no_ano(func): def login_required_if_no_ano(func):
@wraps(func) @wraps(func)
@ -36,6 +36,51 @@ def login_required_if_no_ano(func):
return decorated_view return decorated_view
def requires_basic_auth_if_no_ano(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or auth.type != 'basic':
if config.config_anonbrowse != 1:
user = load_user_from_reverse_proxy_header(request)
if user:
return f(*args, **kwargs)
return _authenticate()
else:
return f(*args, **kwargs)
if config.config_login_type == constants.LOGIN_LDAP and services.ldap:
result, error = services.ldap.bind_user(auth.username, auth.password)
if result:
user = _fetch_user_by_name(auth.username)
login_user(user)
else:
log.error(error)
user = None
else:
user = _load_user_from_auth_header(auth.username, auth.password)
if not user:
return _authenticate()
return f(*args, **kwargs)
return decorated
def _load_user_from_auth_header(username, password):
user = _fetch_user_by_name(username)
if bool(user and check_password_hash(str(user.password), password)):
login_user(user)
return user
else:
ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
log.warning('OPDS Login failed for user "%s" IP-address: %s', username, ip_address)
return None
def _authenticate():
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def _fetch_user_by_name(username): def _fetch_user_by_name(username):
return ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.lower()).first() return ub.session.query(ub.User).filter(func.lower(ub.User.name) == username.lower()).first()
@ -43,45 +88,20 @@ def _fetch_user_by_name(username):
@lm.user_loader @lm.user_loader
def load_user(user_id): def load_user(user_id):
return ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() user = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first()
return user
@lm.request_loader @lm.request_loader
def load_user_from_request(request): def load_user_from_reverse_proxy_header(req):
if config.config_allow_reverse_proxy_header_login: if config.config_allow_reverse_proxy_header_login:
rp_header_name = config.config_reverse_proxy_login_header_name rp_header_name = config.config_reverse_proxy_login_header_name
if rp_header_name: if rp_header_name:
rp_header_username = request.headers.get(rp_header_name) rp_header_username = req.headers.get(rp_header_name)
if rp_header_username: if rp_header_username:
user = _fetch_user_by_name(rp_header_username) user = _fetch_user_by_name(rp_header_username)
if user: if user:
login_user(user) login_user(user)
return user return user
return None
auth_header = request.headers.get("Authorization")
if auth_header:
user = load_user_from_auth_header(auth_header)
if user:
return user
return
def load_user_from_auth_header(header_val):
if header_val.startswith('Basic '):
header_val = header_val.replace('Basic ', '', 1)
basic_username = basic_password = '' # nosec
try:
header_val = base64.b64decode(header_val).decode('utf-8')
# Users with colon are invalid: rfc7617 page 4
basic_username = header_val.split(':', 1)[0]
basic_password = header_val.split(':', 1)[1]
except (TypeError, UnicodeDecodeError, binascii.Error):
pass
user = _fetch_user_by_name(basic_username)
if user and config.config_login_type == constants.LOGIN_LDAP and services.ldap:
if services.ldap.bind_user(str(user.password), basic_password):
return user
if user and check_password_hash(str(user.password), basic_password):
return user
return

View File

@ -31,6 +31,7 @@ from flask_babel import gettext as _
from flask_babel import lazy_gettext as N_ from flask_babel import lazy_gettext as N_
from flask_babel import get_locale from flask_babel import get_locale
from flask_login import login_user, logout_user, login_required, current_user from flask_login import login_user, logout_user, login_required, current_user
from flask_limiter import RateLimitExceeded
from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError
from sqlalchemy.sql.expression import text, func, false, not_, and_, or_ from sqlalchemy.sql.expression import text, func, false, not_, and_, or_
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
@ -56,7 +57,8 @@ from .kobo_sync_status import remove_synced_book
from .render_template import render_title_template from .render_template import render_title_template
from .kobo_sync_status import change_archived_books from .kobo_sync_status import change_archived_books
from . import limiter from . import limiter
from flask_limiter import RateLimitExceeded from .services.worker import WorkerThread
from .tasks_status import render_task_status
feature_support = { feature_support = {
@ -394,7 +396,7 @@ def render_books_list(data, sort_param, book_id, page):
elif data == "archived": elif data == "archived":
return render_archived_books(page, order) return render_archived_books(page, order)
elif data == "search": elif data == "search":
term = (request.args.get('query') or '') term = json.loads(flask_session.get('query', ''))
offset = int(int(config.config_books_per_page) * (page - 1)) offset = int(int(config.config_books_per_page) * (page - 1))
return render_search_results(term, offset, order, config.config_books_per_page) return render_search_results(term, offset, order, config.config_books_per_page)
elif data == "advsearch": elif data == "advsearch":

BIN
library/metadata.db Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ Flask-Babel>=0.11.1,<3.1.0
Flask-Login>=0.3.2,<0.6.3 Flask-Login>=0.3.2,<0.6.3
Flask-Principal>=0.3.2,<0.5.1 Flask-Principal>=0.3.2,<0.5.1
backports_abc>=0.4 backports_abc>=0.4
Flask>=1.0.2,<2.2.0 Flask>=1.0.2,<2.3.0
iso-639>=0.4.5,<0.5.0 iso-639>=0.4.5,<0.5.0
PyPDF>=3.0.0,<3.3.0 PyPDF>=3.0.0,<3.3.0
pytz>=2016.10 pytz>=2016.10