1
0
Fork 0
mirror of https://github.com/Yetangitu/owncloud-apps.git synced 2025-10-02 14:49:17 +02:00

- add rudimentary epub parser for metadata extraction

- add cover images
 - add configurable preview settings (should probably be in core or in a separate app)
 - add some metadata to feed template (file size, type and filename)
This commit is contained in:
frankdelange 2014-12-12 19:24:36 +01:00
parent 64cfb7925e
commit 007b7c7791
13 changed files with 704 additions and 12 deletions

30
files_opds/admin.php Normal file
View file

@ -0,0 +1,30 @@
<?php
/**
* ownCloud - Files_Opds app
*
* Copyright (c) 2014 Frank de Lange
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_Opds;
\OCP\Util::addScript('files_opds', 'admin');
\OCP\Util::addStyle('files_opds', 'settings');
$formats = array(
["epub" => Config::getPreview('OC\Preview\Epub') ? 1 : 0 ],
["pdf" => Config::getPreview('OC\Preview\PDF') ? 1 : 0],
["openoffice" => Config::getPreview('OC\Preview\OpenOffice') ? 1 : 0],
["msoffice" => Config::getPreview('OC\Preview\MSOfficeDoc') ? 1 : 0]
);
$tmpl = new \OCP\Template('files_opds', 'admin');
$tmpl->assign('previewFormats', $formats);
$tmpl->assign('cover-x', Config::getApp('cover-x', '200'));
$tmpl->assign('cover-y', Config::getApp('cover-y', '200'));
$tmpl->assign('thumb-x', Config::getApp('thumb-x', '36'));
$tmpl->assign('thumb-y', Config::getApp('thumb-y', '36'));
return $tmpl->fetchPage();

54
files_opds/ajax/admin.php Normal file
View file

@ -0,0 +1,54 @@
<?php
/**
* ownCloud - Files_Opds App
*
* @author Frank de Lange
* @copyright 2014 Frank de Lange
*
* This file is licensed under the Affero General Public License version 3 or
* later.
*/
namespace OCA\Files_Opds;
\OCP\JSON::callCheck();
\OCP\JSON::checkLoggedIn();
$l = new \OC_L10N('files_opds');
if (isset($_POST['opdsCoverX'])) {
// set dimensions, using sane defaults just in case
$opdsCoverX = isset($_POST['opdsCoverX']) ? (int) $_POST['opdsCoverX'] : 200;
$opdsCoverY = isset($_POST['opdsCoverY']) ? (int) $_POST['opdsCoverY'] : 200;
$opdsThumbX = isset($_POST['opdsThumbX']) ? (int) $_POST['opdsThumbX'] : 36;
$opdsThumbY = isset($_POST['opdsThumbY']) ? (int) $_POST['opdsThumbY'] : 36;
Config::setApp('cover-x', $opdsCoverX);
Config::setApp('cover-y', $opdsCoverY);
Config::setApp('thumb-x', $opdsThumbX);
Config::setApp('thumb-y', $opdsThumbX);
} else {
// set preview preferences
$opdsPreviewEpub = $_POST['opdsPreviewEpub'];
$opdsPreviewPdf = $_POST['opdsPreviewPdf'];
$opdsPreviewOpenOffice = $_POST['opdsPreviewOpenOffice'];
$opdsPreviewMsOffice = $_POST['opdsPreviewMsOffice'];
Config::setPreview('OC\Preview\Epub',$opdsPreviewEpub);
Config::setPreview('OC\Preview\PDF',$opdsPreviewPdf);
Config::setPreview('OC\Preview\OpenDocument',$opdsPreviewOpenOffice);
Config::setPreview('OC\Preview\StarOffice',$opdsPreviewOpenOffice);
Config::setPreview('OC\Preview\MSOfficeDoc',$opdsPreviewMsOffice);
Config::setPreview('OC\Preview\MSOffice2003',$opdsPreviewMsOffice);
Config::setPreview('OC\Preview\MSOffice2007',$opdsPreviewMsOffice);
}
\OCP\JSON::success(
array(
'data' => array('message'=> $l->t('Settings updated successfully.'))
)
);
exit();

View file

@ -2,6 +2,14 @@
$l = OC_L10N::get('files_opds');
require 'files_opds/lib/epub-preview.php';
\OCP\App::registerPersonal('files_opds', 'personal');
\OCP\App::registerAdmin('files_opds', 'admin');
/* enable preview providers... */
// \OCA\Files_Opds\Config::setPreview('OC\Preview\Epub',true);
/* ..and register preview provider */
\OC\Preview::registerProvider('OC\Preview\Epub');

View file

@ -4,7 +4,7 @@
<name>OPDS catalog</name>
<description>Personal OPDS catalog</description>
<licence>AGPL</licence>
<version>0.1.2</version>
<version>0.2</version>
<author>Frank de Lange</author>
<requiremin>7.0</requiremin>
<shipped>true</shipped>

View file

@ -0,0 +1,7 @@
#opds .indent {
padding-left: 1em;
}
#opds .double-indent {
padding-left: 2em;
}

53
files_opds/js/admin.js Normal file
View file

@ -0,0 +1,53 @@
$(document).ready(function(){
// save settings
var opdsAdminSettings = {
save : function() {
var epub = document.getElementById('opds-preview-epub').checked ? 'true' : 'false';
var pdf = document.getElementById('opds-preview-pdf').checked ? 'true' : 'false';
var openoffice = document.getElementById('opds-preview-openoffice').checked ? 'true' : 'false';
var msoffice = document.getElementById('opds-preview-msoffice').checked ? 'true' : 'false';
var data = {
opdsPreviewEpub : epub,
opdsPreviewPdf : pdf,
opdsPreviewOpenOffice : openoffice,
opdsPreviewMsOffice : msoffice
};
OC.msg.startSaving('#opds-admin .msg');
$.post(OC.filePath('files_opds', 'ajax', 'admin.php'), data, opdsAdminSettings.afterSave);
},
afterSave : function(data){
OC.msg.finishedSaving('#opds .msg', data);
}
};
var opdsAdminCoverSettings = {
save : function() {
var data = {
opdsCoverX : $('#opds-cover-x').val(),
opdsCoverY : $('#opds-cover-y').val(),
opdsThumbX : $('#opds-thumb-x').val(),
opdsThumbY : $('#opds-thumb-y').val()
};
OC.msg.startSaving('#opds-admin .msg');
$.post(OC.filePath('files_opds', 'ajax', 'admin.php'), data, opdsAdminCoverSettings.afterSave);
},
afterSave : function(data){
OC.msg.finishedSaving('#opds .msg', data);
}
};
$('#opds-preview-epub').on("change", opdsAdminSettings.save);
$('#opds-preview-pdf').on("change", opdsAdminSettings.save);
$('#opds-preview-openoffice').on("change", opdsAdminSettings.save);
$('#opds-preview-msoffice').on("change", opdsAdminSettings.save);
$('#opds-cover-x,#opds-cover-y,#opds-thumb-x,#opds-thumb-y').blur(opdsAdminCoverSettings.save);
$('#opds-cover-x,#opds-cover-y,#opds-thumb-x,#opds-thumb-y').keypress(function( event ) {
if (event.which == 13) {
event.preventDefault();
opdsAdminCoverSettings.save();
}
});
});

View file

@ -38,4 +38,74 @@ class Config
public static function set($key, $value) {
return \OCP\Config::setUserValue(\OCP\User::getUser(), 'files_opds', $key, $value);
}
/**
* @brief get app config value
*
* @param string $key value to retrieve
* @param string $default default value to use
* @return string retrieved value or default
*/
public static function getApp($key, $default) {
return \OCP\Config::getAppValue('files_opds', $key, $default);
}
/**
* @brief set app config value
*
* @param string $key key for value to change
* @param string $value value to use
* @return bool success
*/
public static function setApp($key, $value) {
return \OCP\Config::setAppValue('files_opds', $key, $value);
}
/**
* @brief get preview status
*
* @param string format
* @return bool (true = enabled, false = disabled)
*/
public static function getPreview($format) {
$enablePreviewProviders = \OCP\Config::getSystemValue('enabledPreviewProviders', null);
if (!($enablePreviewProviders === null)) {
return in_array($format, $enablePreviewProviders);
}
return false;
}
/**
* @brief enable/disable preview for selected format
*
* @param string format
* @param bool enable (true = enable, false = disable, default = false)
* @return bool
*/
public static function setPreview($format, $enable = 'false') {
$enablePreviewProviders = \OCP\Config::getSystemValue('enabledPreviewProviders', null);
if ($enable == 'true') {
if ($enablePreviewProviders === null) {
// set up default providers
$enablePreviewProviders = array();
array_push($enablePreviewProviders,
'OC\Preview\Image',
'OC\Preview\MP3',
'OC\Preview\TXT',
'OC\Preview\MarkDown');
}
if (!(in_array($format,$enablePreviewProviders))) {
array_push($enablePreviewProviders, $format);
}
} else {
if (!($enablePreviewProviders == null)) {
$enablePreviewProviders = array_diff($enablePreviewProviders, array($format));
}
}
if (!(\OCP\Config::setSystemValue('enabledPreviewProviders', $enablePreviewProviders))) {
logWarn("Failed to enable " . $format . " preview provider (config.php readonly?)");
return true;
}
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* ownCloud - Files_Opds App
*
* @author Frank de Lange
* @copyright 2014 Frank de Lange
*
* This file is licensed under the Affero General Public License version 3 or
* later.
*/
namespace OC\Preview;
/**
* Epub preview - returns cover or null
*/
class Epub extends Provider {
public function getMimeType() {
return '/application\/epub\+zip/';
}
public function getThumbnail($path, $maxX, $maxY, $scalingup, $fileview) {
//get fileinfo
$fileInfo = $fileview->getFileInfo($path);
if(!$fileInfo) {
return false;
}
$absPath = $fileview->toTmpFile($path);
$epub = new \OCA\Files_Opds\Epub($absPath);
$cover = $epub->Cover();
if ($cover) {
$image = new \OC_Image();
$image->loadFromData($cover['data']);
}
return (($cover !== null) && $image->valid()) ? $image : false;
}
}

332
files_opds/lib/epub.php Normal file
View file

@ -0,0 +1,332 @@
<?php
/**
* ownCloud - Files_Opds App
*
* @author Frank de Lange
* @copyright 2014 Frank de Lange
*
* This file is licensed under the Affero General Public License version 3 or
* later.
*/
namespace OCA\Files_Opds;
use \DOMXpath;
use \DOMElement;
use \DOMDocument;
/**
* Epub class, a simpleminded read-only epub parser intended for metadata extraction
* based on https://github.com/splitbrain/php-epub-meta
*/
class Epub {
protected $xml;
protected $xpath;
protected $file;
protected $meta;
/**
* @brief Constructor
*
* @param string $file path to epub file to work on
* @throws Exception if metadata could not be loaded
*/
public function __construct($file) {
// open file
$this->file = $file;
$zip = new \ZipArchive();
if(!($zip->open($this->file))){
\OC_Log::write('epub', "Failed to read epub file", \OC_Log::ERROR);
return false;
}
// read container data
$data = $zip->getFromName('META-INF/container.xml');
if($data == false){
\OC_Log::write('epub', "Failed to access epub container data", \OC_Log::ERROR);
return false;
}
$xml = new DOMDocument();
$xml->registerNodeClass('DOMElement','\OCA\Files_Opds\EPubDOMElement');
$xml->loadXML($data);
$xpath = new EPubDOMXPath($xml);
$nodes = $xpath->query('//n:rootfiles/n:rootfile[@media-type="application/oebps-package+xml"]');
$this->meta = $nodes->item(0)->attr('full-path');
// load metadata
$data = $zip->getFromName($this->meta);
if(!$data){
\OC_Log::write('epub', 'Failed to access epub metadata', \OC_Log::ERROR);
return false;
}
$this->xml = new \DOMDocument();
$this->xml->registerNodeClass('DOMElement','\OCA\Files_Opds\EPubDOMElement');
$this->xml->loadXML($data);
$this->xml->formatOutput = true;
$this->xpath = new EPubDOMXPath($this->xml);
$zip->close();
}
/**
* @brief file name getter
* @return string filename
*/
public static function file() {
return $this->file;
}
/**
* @brief get author(s)
*
* @return array $authors
*/
public static function Authors() {
// read current data
$rolefix = false;
$authors = array();
$nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]');
if($nodes->length == 0){
// no nodes where found, let's try again without role
$nodes = $this->xpath->query('//opf:metadata/dc:creator');
$rolefix = true;
}
foreach($nodes as $node){
$name = $node->nodeValue;
$as = $node->attr('opf:file-as');
if(!$as){
$as = $name;
$node->attr('opf:file-as',$as);
}
if($rolefix){
$node->attr('opf:role','aut');
}
$authors[$as] = $name;
}
return $authors;
}
/**
* @brief get book title
*
* @param string $title
*/
public function Title(){
return $this->get('dc:title');
}
/**
* @brief get language
*
* @param string $lang
*/
public function Language(){
return $this->get('dc:language');
}
/**
* @brief get publisher info
*
* @return string $publisher
*/
public function Publisher(){
return $this->get('dc:publisher');
}
/**
* @brief get copyright info
*
* @return string $rights
*/
public function Copyright(){
return $this->get('dc:rights');
}
/**
* @brief get description
*
* @return string $description
*/
public function Description(){
return $this->get('dc:description');
}
/**
* @brief get ISBN number
*
* @return string $isbn
*/
public function ISBN(){
return $this->get('dc:identifier','opf:scheme','ISBN');
}
/**
* @brief get Google Books ID
*
* @return string $google
*/
public function Google(){
return $this->get('dc:identifier','opf:scheme','GOOGLE');
}
/**
* @brief get Amazon ID
*
* @return string $amazon
*/
public function Amazon(){
return $this->get('dc:identifier','opf:scheme','AMAZON');
}
/**
* @brief get subjects (aka. tags)
*
* @return array $subjects
*/
public function Subjects(){
$subjects = array();
$nodes = $this->xpath->query('//opf:metadata/dc:subject');
foreach($nodes as $node){
$subjects[] = $node->nodeValue;
}
return $subjects;
}
/**
* @brief get cover data
*
* Returns an associative array with the following keys:
*
* mime - filetype (usually image/jpeg)
* data - binary image data
* found - internal path, or false if no image is set in epub
*
* @return array or null
*/
public function Cover(){
$nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]');
if($nodes->length) {
$coverid = (String) $nodes->item(0)->attr('opf:content');
if ($coverid) {
$nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]');
if ($nodes->length) {
$mime = $nodes->item(0)->attr('opf:media-type');
$path = $nodes->item(0)->attr('opf:href');
$path = dirname('/'.$this->meta).'/'.$path; // image path is relative to meta file
$path = ltrim($path,'/');
$zip = new \ZipArchive();
if($zip->open($this->file)){
$data = $zip->getFromName($path);
return array(
'mime' => $mime,
'data' => $data,
'found' => $path
);
}
}
}
} else {
return null;
}
}
/**
* @brief simple getter for simple meta attributes
*
* It should only be used for attributes that are expected to be unique
*
* @param string $item XML node to get
* @param string $att Attribute name
* @param string $aval Attribute value
* @return string node value
*/
protected function get($item, $att=false, $aval=false){
$xpath = '//opf:metadata/'.$item;
if ($att) {
$xpath .= "[@$att=\"$aval\"]";
}
$nodes = $this->xpath->query($xpath);
if($nodes->length){
return $nodes->item(0)->nodeValue;
}else{
return '';
}
}
}
class EPubDOMXPath extends DOMXPath {
public function __construct(DOMDocument $doc){
parent::__construct($doc);
if(is_a($doc->documentElement, '\OCA\Files_Opds\EPubDOMElement')){
foreach($doc->documentElement->namespaces as $ns => $url){
$this->registerNamespace($ns,$url);
}
}
}
}
class EPubDOMElement extends DOMElement {
public $namespaces = array(
'n' => 'urn:oasis:names:tc:opendocument:xmlns:container',
'opf' => 'http://www.idpf.org/2007/opf',
'dc' => 'http://purl.org/dc/elements/1.1/'
);
public function __construct($name, $value='', $namespaceURI=''){
list($ns,$name) = $this->splitns($name);
$value = htmlspecialchars($value);
if(!$namespaceURI && $ns){
$namespaceURI = $this->namespaces[$ns];
}
parent::__construct($name, $value, $namespaceURI);
}
/**
* @brief split given name in namespace prefix and local part
*
* @param string $name
* @return array (namespace, name)
*/
public function splitns($name){
$list = explode(':',$name,2);
if(count($list) < 2) array_unshift($list,'');
return $list;
}
/**
* @brief simple EPub namespace aware attribute getter
*
* @param string attribute
* @return string attribute value
*/
public function attr($attr){
list($ns,$attr) = $this->splitns($attr);
$nsuri = '';
if($ns){
$nsuri = $this->namespaces[$ns];
if(!$this->namespaceURI){
if($this->isDefaultNamespace($nsuri)){
$nsuri = '';
}
}elseif($this->namespaceURI == $nsuri){
$nsuri = '';
}
}
if($nsuri){
return $this->getAttributeNS($nsuri,$attr);
}else{
return $this->getAttribute($attr);
}
}
}

View file

@ -18,7 +18,7 @@ namespace OCA\Files_Opds;
class Files extends \OCA\Files\Helper
{
/**
* Formats the file info to be returned as OPDS to the client.
* Formats the file info to be returned as OPDS to the client
*
* @param \OCP\Files\FileInfo $i
* @return array formatted file info
@ -28,11 +28,16 @@ class Files extends \OCA\Files\Helper
$entry['id'] = $i['fileid'];
$entry['mtime'] = $i['mtime'] * 1000;
$entry['icon'] = self::determineIcon($i);
$entry['name'] = $i->getName();
$entry['mimetype'] = $i['mimetype'];
$entry['size'] = $i['size'];
$entry['type'] = $i['type'];
if ($i['type'] === 'file') {
$entry['mimetype'] = $i['mimetype'];
$entry['preview'] = self::getPreview($i);
$entry['thumbnail'] = self::getThumbnail($i);
$entry['humansize'] = \OC_Helper::humanFileSize($i['size']);
} else {
$entry['icon'] = self::determineIcon($i);
}
return $entry;
}
@ -54,6 +59,33 @@ class Files extends \OCA\Files\Helper
return $files;
}
/**
* @brief get preview for file
* @param \OCP\Files\FileInfo $i
* @return string preview URL
*/
public static function getPreview($i) {
if (\OC::$server->getPreviewManager()->isMimeSupported($i['mimetype'])) {
return \OC_Helper::linkToRoute( 'core_ajax_preview', array('x' => Config::getApp('cover-x', '200'), 'y' => Config::getApp('cover-y', '200'), 'file' => \OC\Files\Filesystem::normalizePath(\OC\Files\Filesystem::getPath($i['fileid']))));
} else {
return self::determineIcon($i);
}
}
/**
* @brief get thumbnail for file
* @param \OCP\Files\FileInfo $i
* @return string preview URL
*/
public static function getThumbnail($i) {
if (\OC::$server->getPreviewManager()->isMimeSupported($i['mimetype'])) {
return \OC_Helper::linkToRoute( 'core_ajax_preview', array('x' => Config::getApp('thumb-x', '36'), 'y' => Config::getApp('thumb-y', '36'), 'file' => \OC\Files\Filesystem::normalizePath(\OC\Files\Filesystem::getPath($i['fileid']))));
} else {
return self::determineIcon($i);
}
}
/*
* @brief check if $child is a subdirectory of $parent
*

View file

@ -0,0 +1,48 @@
<?php
/**
* ownCloud - Files_Opds App
*
* @author Frank de Lange
* @copyright 2014 Frank de Lange
*
* This file is licensed under the Affero General Public License version 3 or
* later.
*/
function checkBox($format) {
foreach($format as $name => $enabled) {
echo '<input type="checkbox" id="opds-preview-' . $name . '" name="opds-preview-' . $name . '" ' . ($enabled == 1 ? 'checked >' : '>');
echo '<label for="opds-preview-' . $name . '">' . $name . '</label>';
}
}
?>
<div class="section" id="opds">
<h2><?php p($l->t('OPDS')); ?></h2>
<h3>Enable preview for:<span class="msg"></span></h3>
<div class="indent">
<?php foreach ($_['previewFormats'] as $format): ?>
<div>
<?php checkBox($format); ?>
</div>
<?php endforeach; ?>
</div>
<br>
<div>
<p>Cover size</p>
<label for="opds-cover-x"><?php p($l->t('width')) ?></label>
<input type="text" id="opds-cover-x" title="<?php p($l->t("Enter cover image width in pixels")); ?>" value="<?php p($_['cover-x']) ?>" />
<label for="opds-cover-y"><?php p($l->t('height')) ?></label>
<input type="text" id="opds-cover-y" title="<?php p($l->t("Enter cover image height in pixels")); ?>" value="<?php p($_['cover-y']) ?>" />
</div>
<div>
<p>Cover thumbnail size</p>
<label for="opds-thumb-x"><?php p($l->t('width')) ?></label>
<input type="text" id="opds-thumb-x" title="<?php p($l->t("Enter thumbnail width in pixels")); ?>" value="<?php p($_['thumb-x']) ?>" />
<label for="opds-thumb-y"><?php p($l->t('height')) ?></label>
<input type="text" id="opds-thumb-y" title="<?php p($l->t("Enter thumbnail height in pixels")); ?>" value="<?php p($_['thumb-y']) ?>" />
</div>
</div>

View file

@ -10,11 +10,19 @@
* later.
*/
function formatMetadata($humansize,$mimetype,$name) {
return "Size: " . $humansize . "\n"
. "Type: " . $mimetype . "\n"
. "Filename: " . $name;
}
echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/terms/"
xmlns:dcterms="http://purl.org/dc/terms/"
xmlns:opds="http://opds-spec.org/2010/catalog">
<id>id:<?php p($_['feed_id']); ?></id>
<title><?php p($l->t("%s's library", array($_['user']))); ?></title>
@ -54,19 +62,20 @@ echo '<?xml version="1.0" encoding="UTF-8"?>';
<title><?php p($file['name']); ?></title>
<updated><?php p(date("Y-m-d\TH:i:sP", $file['mtime'])); ?></updated>
<id>id:<?php p($file['id']); ?></id>
<dcterms:extent><?php p($file['humansize']); ?></dcterms:extent>
<link type="<?php p($file['mimetype']); ?>"
rel="alternate"
href="?id=<?php p($file['id']); ?>"/>
<link type="<?php p($file['mimetype']); ?>"
rel="http://opds-spec.org/acquisition/open-access"
href="?id=<?php p($file['id']); ?>"/>
<link href="<?php p($file['icon']); ?>"
<link href="<?php p($file['preview']); ?>"
rel="http://opds-spec.org/image"
type="image/jpeg" />
<link href="<?php p($file['icon']); ?>"
<link href="<?php p($file['thumbnail']); ?>"
rel="http://opds-spec.org/image/thumbnail"
type="image/jpeg" />
<content type="text"></content>
<content type="text"><?php p(formatMetadata($file['humansize'],$file['mimetype'],$file['name'])); ?></content>
</entry>
<?php endforeach; ?>
<?php else: ?>
@ -89,19 +98,20 @@ echo '<?xml version="1.0" encoding="UTF-8"?>';
<title><?php p($file['name']); ?></title>
<updated><?php p(date("Y-m-d\TH:i:sP", $file['mtime'])); ?></updated>
<id>id:<?php p($file['id']); ?></id>
<dcterms:extent><?php p($file['humansize']); ?></dcterms:extent>
<link type="<?php p($file['mimetype']); ?>"
rel="alternate"
href="?id=<?php p($file['id']); ?>"/>
<link type="<?php p($file['mimetype']); ?>"
rel="http://opds-spec.org/acquisition/open-access"
href="?id=<?php p($file['id']); ?>"/>
<link href="<?php p($file['icon']); ?>"
<link href="<?php p($file['preview']); ?>"
rel="http://opds-spec.org/image"
type="image/jpeg" />
<link href="<?php p($file['icon']); ?>"
<link href="<?php p($file['thumbnail']); ?>"
rel="http://opds-spec.org/image/thumbnail"
type="image/jpeg" />
<content type="text"></content>
<content type="text"><?php p(formatMetadata($file['humansize'],$file['mimetype'],$file['name'])); ?></content>
</entry>
<?php endif; ?>
<?php endforeach; ?>

View file

@ -12,7 +12,7 @@
?>
<div class="section" id="opds-personal">
<div class="section" id="opds">
<h2><?php p($l->t('OPDS')); ?></h2>
<div>
<input id="opds-enable" name="opds-enable" value="<?php p($_['opdsEnable-value']) ?>" <?php p($_['opdsEnable-checked']) ?> type="checkbox">