Compare commits

...

17 Commits

Author SHA1 Message Date
Uwe Steinmann
ff4b3d7722 update release date 2025-02-27 13:47:53 +01:00
Uwe Steinmann
1c09c1ba4e fix getting uploaded files with PSR7 2025-01-02 17:15:12 +01:00
Uwe Steinmann
54d4d2c41a new version 1.4.0 2025-01-02 17:11:10 +01:00
Uwe Steinmann
6633e78ef8 lots of changes to be compatible with Slim 4 which was introduced 2025-01-02 17:10:52 +01:00
Uwe Steinmann
9ba5882aa9 works only till 5.1.36 and 6.0.29 2024-12-09 10:31:02 +01:00
Uwe Steinmann
feccb4ac65 add changes of 1.3.0 2024-12-09 10:27:00 +01:00
Uwe Steinmann
413877b537 update release date 2024-12-09 10:26:49 +01:00
Uwe Steinmann
4390b57036 slightly better phrasing 2024-12-09 10:17:09 +01:00
Uwe Steinmann
b7b3e20cf4 access container elements with get() add set them with set() 2024-12-09 10:16:33 +01:00
Uwe Steinmann
3fb56643ee add changes of 1.3.0 2024-11-27 17:19:25 +01:00
Uwe Steinmann
c8c5a789ae add method documentListRowExtraContent() for showing correspondent 2024-11-27 17:17:48 +01:00
Uwe Steinmann
7bc95887cb add search for rule 8 and 9 2024-11-11 14:58:13 +01:00
Uwe Steinmann
3a73820fee add translation for rule 8 and 9 2024-11-11 14:57:51 +01:00
Uwe Steinmann
caf2e49d3b add page with list of stored views 2024-11-11 14:42:18 +01:00
Uwe Steinmann
0ee9bfee6b start new version 1.3.0 2024-11-11 14:41:10 +01:00
Uwe Steinmann
eef4f46687 fix typos 2024-07-16 13:30:10 +02:00
Uwe Steinmann
a551ea2587 minor fixes and more information on token generation 2024-07-16 13:28:13 +02:00
8 changed files with 875 additions and 285 deletions

View File

@ -67,11 +67,13 @@ to setup fulltext search before using it.
### Authentication
Paperless uses a token based or http basic authentication. Both are
implemented by another slim middleware. There is no session, but the
token is encrypted and stores all the required data to identify the user.
The password to encrypt the token can be set in the configuration, just
like the expiration date of the token. Once the password changes all
token will become invalid and users will have to relogin.
implemented in SeedDMS by an additional slim middleware. There is no
session, but the token is encrypted and stores all the required data
to identify the user, including a lifetime which can be set in the
configuration. Once that lifetime has ended the token becomes invalid
and the user has to relogin. The password to encrypt the token can
also be set in the configuration. Once the password changes all token
will become invalid and users will have to relogin.
### Archive
@ -98,7 +100,7 @@ be easily simulated with a custom attribute. Since version 1.0.1 of this
extension the configuration contains a parameter for an attribute which
must be a value set containing the different document types. Do *not* make
it a multi value attribute, because a document in Paperless may have only
one type. Any more types while you need it or changing the order is possible.
one type. Adding more types when you need it or changing the order is possible.
### Correspondents

View File

@ -1,3 +1,17 @@
Changes in version 1.4.0
==========================
- lots of changes to be compatible with Slim 4 which was introduced
in SeedDMS 5.1.37 and 6.0.30
Changes in version 1.3.0
==========================
- use get() and set() to access elements of container
(in preparation for Slim4)
- add page for listing stored views
- show correspondent as badge in document list view
Changes in version 1.2.3
==========================

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,8 @@ $EXT_CONF['paperless'] = array(
'title' => 'Paperless RestAPI',
'description' => 'This extension adds additional rest api routes to make it behave like a paperless server. Just use the regular paperless apps, .e.g paperless mobile to access SeedDMS.',
'disable' => false,
'version' => '1.2.3',
'releasedate' => '2024-07-16',
'version' => '1.4.0',
'releasedate' => '2025-02-27',
'author' => array('name'=>'Uwe Steinmann', 'email'=>'uwe@steinmann.cx', 'company'=>'MMK GmbH'),
'config' => array(
'rootfolder' => array(
@ -73,7 +73,7 @@ $EXT_CONF['paperless'] = array(
),
),
'constraints' => array(
'depends' => array('php' => '7.4.0-', 'seeddms' => array('5.1.34-5.1.99', '6.0.27-6.0.99', '6.1.0-6.1.99')),
'depends' => array('php' => '7.4.0-', 'seeddms' => array('5.1.34-5.1.37', '6.0.30-6.0.99', '6.1.0-6.1.99')),
),
'icon' => 'icon.svg',
'changelog' => 'changelog.md',

View File

@ -9,6 +9,20 @@ $__lang['en_GB'] = array(
'paperless_token_has_expired'=>'Token has expired. Login again before you proceed.',
'paperless_jwtsecret_not_set'=>'The secret for the JSON Web Token is not set. This is required for successful login.',
'paperless_needs_fulltextsearch'=>'The extension needs fulltext search to be on.',
'paperless_views'=>'Paperless',
'paperless_view_name'=>'Name',
'paperless_view_rules'=>'Rules',
'paperless_view_sort_by'=>'Sort by',
'paperless_view_dashboard'=>'Dashboard',
'paperless_view_sidebar'=>'Sidebar',
'paperless_view_rule_13'=>'Added until',
'paperless_view_rule_14'=>'Added from',
'paperless_view_rule_8'=>'Created until',
'paperless_view_rule_9'=>'Created from',
'paperless_view_rule_6'=>'Category',
'paperless_view_rule_3'=>'Correspondent',
'paperless_view_rule_19'=>'Title & Content',
'paperless_view_rule_20'=>'Advanced Search',
);
$__lang['de_DE'] = array(
'paperless'=>'Paperless',
@ -20,5 +34,19 @@ $__lang['de_DE'] = array(
'paperless_token_has_expired'=>'Token abgelaufen. Melden Sie sich bitte neu an.',
'paperless_jwtsecret_not_set'=>'Das Geheimnis des JSON Web Token ist nicht gesetzt. Dies ist für eine erfolgreiche Anmeldung erforderlich.',
'paperless_needs_fulltextsearch'=>'Die Erweiterung erfordert eine konfigurierte Volltextsuche .',
'paperless_views'=>'Paperless',
'paperless_view_name'=>'Name',
'paperless_view_rules'=>'Regeln',
'paperless_view_sort_by'=>'Sortiere nach',
'paperless_view_dashboard'=>'Dashboard',
'paperless_view_sidebar'=>'Sidebar',
'paperless_view_rule_13'=>'Hinzugefügt bis',
'paperless_view_rule_14'=>'Hinzugefügt von',
'paperless_view_rule_8'=>'Ausgestellt bis',
'paperless_view_rule_9'=>'Ausgestellt von',
'paperless_view_rule_6'=>'Kategorie',
'paperless_view_rule_3'=>'Korrepondent',
'paperless_view_rule_19'=>'Titel & Inhalt',
'paperless_view_rule_20'=>'Erweiterte Suche',
);

View File

@ -0,0 +1,69 @@
<?php
/**
* MyDMS. Document Management System
* Copyright (C) 2002-2005 Markus Westphal
* Copyright (C) 2006-2008 Malcolm Cowe
* Copyright (C) 2010-2011 Matteo Lucarelli
* Copyright (C) 2010-2024 Uwe Steinmann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
if(!empty($_SERVER['SEEDDMS_HOME']))
require_once($_SERVER['SEEDDMS_HOME'].'/inc/inc.Settings.php');
else
require_once("../../../inc/inc.Settings.php");
require_once("inc/inc.Utils.php");
require_once("inc/inc.LogInit.php");
require_once("inc/inc.Language.php");
require_once("inc/inc.Init.php");
require_once("inc/inc.Extension.php");
require_once("inc/inc.DBInit.php");
require_once("inc/inc.ClassUI.php");
require_once("inc/inc.Authentication.php");
require_once('ext/paperless/class.PaperlessView.php');
$tmp = explode('.', basename($_SERVER['SCRIPT_FILENAME']));
$view = UI::factory($theme, $tmp[1], array('dms' => $dms, 'user' => $user));
$v = new SeedDMS_Version();
if($v->majorVersion() > 5) {
$accessop = new SeedDMS_AccessOperation($dms, $user, $settings);
if (!$accessop->check_view_access($view, $_GET)) {
UI::exitError(getMLText("admin_tools"), getMLText("access_denied"));
}
} else
$accessop = new SeedDMS_AccessOperation($dms, null, $user, $settings);
$pview = null;
if (isset($_GET["view"])) {
$pview = SeedDMS_PaperlessView::getInstance($_GET['view'], $dms);
}
if($view) {
$view->setParam('accessobject', $accessop);
$view->setParam('showtree', showtree());
$view->setParam('pview', $pview);
$view->setParam('fulltextservice', $fulltextservice);
$view->setParam('logger', $logger);
$view->setParam('conversionmgr', $conversionmgr);
$view->setParam('previewWidthList', $settings->_previewWidthList);
$view->setParam('previewconverters', $settings->_converters['preview']);
$view->setParam('timeout', $settings->_cmdTimeout);
$view($_GET);
exit;
}

View File

@ -4,8 +4,8 @@ These scripts can be used to test almost all endpoints of the
paperless-ngx API.
In order to use them, create a file `credentials` and define `AUTH` and
`URL` in it. `URL` is the base url of the restapi service. `AUTH` are the
credentials passed in the http header `Authorization`. It can be a basic
`URL` in it. `URL` is the base url of the restapi service. `AUTH` contains
the credentials passed in the http header `Authorization`. It can be a basic
authentication, a paperless token or a regular SeedDMS key.
Example:

View File

@ -0,0 +1,363 @@
<?php
/**
* Implementation of PaperlessViews view
*
* @category DMS
* @package SeedDMS
* @license GPL 2
* @version @version@
* @author Uwe Steinmann <uwe@steinmann.cx>
* @copyright Copyright (C) 2017 Uwe Steinmann
* @version Release: @package_version@
*/
/**
* Class which outputs the html page for PaperlessViews view
*
* @category DMS
* @package SeedDMS
* @author Uwe Steinmann <uwe@steinmann.cx>
* @copyright Copyright (C) 2017 Uwe Steinmann
* @version Release: @package_version@
*/
class SeedDMS_View_PaperlessViews extends SeedDMS_Bootstrap_Style {
function js() { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$onepage = $this->params['onepage'];
header('Content-Type: application/javascript');
parent::jsTranslations(array('cancel', 'splash_move_document', 'confirm_move_document', 'move_document', 'confirm_transfer_link_document', 'transfer_content', 'link_document', 'splash_move_folder', 'confirm_move_folder', 'move_folder'));
?>
$(document).ready(function() {
$('body').on('click', '.view', function(ev){
ev.preventDefault();
$('#paperless_view.ajax').trigger('update', {view: $(this).data('view')});
});
});
<?php
if($onepage) {
$this->printClickDocumentJs();
}
$this->printDeleteDocumentButtonJs();
} /* }}} */
protected function printListHeader($previewer, $order=false) { /* {{{ */
print "<table class=\"table table-condensed table-sm\">";
print "<thead>\n<tr>\n";
print "<th></th>\n";
if($order) {
$orderby = ''; //$this->params['orderby'];
$orderdir = ''; //$this->params['orderdir'];
print "<th><a data-action=\"".$order."\" data-orderby=\"n\" data-orderdir=\"".($orderdir == 'desc' ? '' : 'desc')."\">".getMLText("name")."</a> ".($orderby == 'n' || $orderby == '' ? ($orderdir == 'desc' ? '<i class="icon-arrow-up"></i>' : '<i class="icon-arrow-down"></i>') : '')." &middot; <a data-action=\"".$order."\" data-orderby=\"u\" data-orderdir=\"".($orderdir == 'desc' ? '' : 'desc')."\">".getMLText("last_update")."</a> ".($orderby == 'u' ? ($orderdir == 'desc' ? '<i class="icon-arrow-up"></i>' : '<i class="icon-arrow-down"></i>') : '')." &middot; <a data-action=\"".$order."\" data-orderby=\"e\" data-orderdir=\"".($orderdir == 'desc' ? '' : 'desc')."\">".getMLText("expires")."</a> ".($orderby == 'e' ? ($orderdir == 'desc' ? '<i class="icon-arrow-up"></i>' : '<i class="icon-arrow-down"></i>') : '')."</th>\n";
} else
print "<th>".getMLText("name")."</th>\n";
if($order)
print "<th><a data-action=\"".$order."\" data-orderby=\"s\" data-orderdir=\"".($orderdir == 'desc' ? '' : 'desc')."\">".getMLText("status")."</a>".($orderby == 's' ? " ".($orderdir == 'desc' ? '<i class="icon-arrow-up"></i>' : '<i class="icon-arrow-down"></i>') : '')."</th>\n";
else
print "<th>".getMLText("status")."</th>\n";
print "<th>".getMLText("action")."</th>\n";
print "</tr>\n</thead>\n<tbody>\n";
} /* }}} */
protected function printListFooter() { /* {{{ */
echo "</tbody>\n</table>";
} /* }}} */
private function getSearchParameter($data) { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$settings = $this->params['settings'];
$categories = array();
$categorynames = array();
$astart = null;
$aend = null;
$mstart = null;
$mend = null;
$startfolder = $dms->getRootFolder();
$rootfolder = $dms->getRootFolder();
$cattrs = [];
$query = '';
foreach ($data['filter_rules'] as $rule) {
switch ($rule['rule_type']) {
case 3:
$correspondent = null;
if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) {
$valueset = $attrdef->getValueSetAsArray();
if(isset($valueset[$rule['value']-1])) {
$correspondent = $valueset[$rule['value']-1];
$cattrs['attr_'.$attrdef->getId()] = $correspondent;
}
}
break;
case 6:
$catid = (int) $rule['value'];
if($catid) {
if($cat = $dms->getDocumentCategory($catid)) {
$categories[] = $cat;
$categorynames[] = $cat->getName();
}
}
break;
case 8:
$aend = makeTsFromDate($rule['value']);
break;
case 9:
$astart = makeTsFromDate($rule['value']);
break;
case 13:
$aend = makeTsFromDate($rule['value']);
break;
case 14:
$astart = makeTsFromDate($rule['value']);
break;
case 19:
$query = $rule['value'];
break;
case 20:
$tmp = explode(',', $rule['value']);
foreach ($tmp as $t) {
if (substr($t, 0, 9) == 'created:[') {
$q = substr($t, 9, -1);
if($x = explode(' to ', $q, 2)) {
$astart = strtotime($x[0]);
$aend = strtotime($x[1])+86400;
}
} elseif (substr($t, 0, 7) == 'added:[') {
$q = substr($t, 7, -1);
if($x = explode(' to ', $q, 2)) {
$astart = strtotime($x[0]);
$aend = strtotime($x[1])+86400;
}
} else {
$query = $t;
}
}
break;
}
}
return [$query, array('record_type'=>['document'], 'status'=>[2], 'user'=>[$user->getLogin()], 'category'=>$categorynames, 'created_start'=>$astart, 'created_end'=>$aend, 'modified_start'=>$mstart, 'modified_end'=>$mend, 'startFolder'=>$startfolder, 'rootFolder'=>$rootfolder, 'attributes'=>$cattrs)];
} /* }}} */
private function printList($data, $previewer) { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$settings = $this->params['settings'];
$fulltextservice = $this->params['fulltextservice'];
$logger = $this->params['logger'];
$order = [];
$order['dir'] = $data['sort_reverse'] ? 'desc' : 'asc';
$orderfield = $data["sort_field"];
if(in_array($orderfield, ['modified', 'created', 'title']))
$order['by'] = $orderfield;
elseif($orderfield == 'added')
$order['by'] = 'created';
elseif($orderfield == 'archive_serial_number')
$order['by'] = 'id';
elseif($orderfield == 'correspondent__name') {
if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) {
$order['by'] = 'attr_'.$attrdef->getId();
}
}
list($query, $searchparams) = $this->getSearchParameter($data);
if($fulltextservice && $index = $fulltextservice->Indexer()) {
$lucenesearch = $fulltextservice->Search();
$limit = 10;
$offset = 0;
$logger->log('Query is '.$query, PEAR_LOG_DEBUG);
/*
$logger->log('User is '.$userobj->getLogin(), PEAR_LOG_DEBUG);
$logger->log('limit is '.$limit, PEAR_LOG_DEBUG);
$logger->log('offset is '.$offset, PEAR_LOG_DEBUG);
*/
$searchresult = $lucenesearch->search($query, $searchparams, array('limit'=>$limit, 'offset'=>$offset), $order, array('no_facets'=>true));
if($searchresult) {
$recs = array();
$facets = $searchresult['facets'];
$dcount = 0;
$fcount = 0;
if($searchresult['hits']) {
$allids = '';
$this->printListHeader($previewer);
foreach($searchresult['hits'] as $hit) {
if($hit['document_id'][0] == 'D') {
if($document = $dms->getDocument((int) substr($hit['document_id'], 1))) {
$document->verifyLastestContentExpriry();
$v = new SeedDMS_Version();
if($v->majorVersion() > 5) {
$document->checkForDueRevisionWorkflow($user);
}
$lc = $document->getLatestContent();
if($document->getAccessMode($user) >= M_READ && $lc) {
$txt = $this->callHook('documentListItem', $document, $previewer, false, 'opentasks');
if(is_string($txt))
echo $txt;
else {
/*
$errormsg = '';
$errorcls = '';
if(!$doc['error']) {
} else {
$errorcls = $doc['error'] < 0 ? " error" : ($doc['error'] === 0 ? " success" : "");
$errormsg = $doc['error'];
}
*/
$extracontent = array();
$extracontent['below_title'] = $this->getListRowPath($document);
echo $this->documentListRowStart($document);
echo $this->documentListRow($document, $previewer, true, 0, $extracontent);
echo $this->documentListRowEnd($document);
}
}
}
}
}
$this->printListFooter();
$logger->log('Result is '.$allids, PEAR_LOG_DEBUG);
}
}
}
} /* }}} */
private function count($data) { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$settings = $this->params['settings'];
$fulltextservice = $this->params['fulltextservice'];
$logger = $this->params['logger'];
list($query, $searchparams) = $this->getSearchParameter($data);
if($fulltextservice && $index = $fulltextservice->Indexer()) {
$lucenesearch = $fulltextservice->Search();
$limit = 10;
$offset = 0;
$logger->log('Query is '.$query, PEAR_LOG_DEBUG);
/*
$logger->log('User is '.$userobj->getLogin(), PEAR_LOG_DEBUG);
$logger->log('limit is '.$limit, PEAR_LOG_DEBUG);
$logger->log('offset is '.$offset, PEAR_LOG_DEBUG);
*/
$searchresult = $lucenesearch->search($query, $searchparams, array('limit'=>0, 'offset'=>0), [], array('no_facets'=>true));
if($searchresult) {
return $searchresult['count'];
}
}
return false;
} /* }}} */
public function list() { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$settings = $this->params['settings'];
$pview = $this->params['pview'];
$cachedir = $this->params['cachedir'];
$conversionmgr = $this->params['conversionmgr'];
$previewwidth = $this->params['previewWidthList'];
$previewconverters = $this->params['previewconverters'];
$timeout = $this->params['timeout'];
$previewer = new SeedDMS_Preview_Previewer($cachedir, $previewwidth, $timeout);
if($conversionmgr)
$previewer->setConversionMgr($conversionmgr);
else
$previewer->setConverters($previewconverters);
if ($pview) {
$data = $pview->getView();
$this->printList($data, $previewer);
}
} /* }}} */
private function getFilterValue($rule) { /* {{{ */
$dms = $this->params['dms'];
$settings = $this->params['settings'];
switch($rule['rule_type']) {
case 13: // Added until
case 14: // Added from
return getReadableDate($rule['value']);
case 3: // Correspondent
if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) {
$valueset = $attrdef->getValueSetAsArray();
if(isset($valueset[$rule['value']-1])) {
return $valueset[$rule['value']-1];
}
}
return '???';
break;
case 6: // Category
return $dms->getDocumentCategory($rule['value'])->getName();
break;
default:
return $rule['value'];
}
} /* }}} */
public function show() { /* {{{ */
$dms = $this->params['dms'];
$user = $this->params['user'];
$settings = $this->params['settings'];
$session = $this->params['session'];
$area = 'my_account';
$this->htmlStartPage(getMLText("paperless_views"), '', '../../');
$this->globalNavigation();
$this->contentStart();
$this->pageNavigation(getMLText($area), $area);
$this->contentHeading(getMLText("paperless_views"));
$this->rowStart();
$this->columnStart(4);
$views = SeedDMS_PaperlessView::getAllInstances($user, $dms);
if($views) {
print "<table id=\"paperlessviews-table\" class=\"table table-condensed table-sm table-hover\">";
print "<thead>\n<tr>\n";
print "<th>".getMLText("paperless_view_name")."</th>\n";
print "<th>".getMLText("paperless_view_rules")."</th>\n";
print "</tr>\n</thead>\n<tbody>\n";
foreach($views as $view) {
$data = $view->getView();
$c = $this->count($data);
echo "<tr class=\"view\" data-view=\"".$data['id']."\"><td><strong>".$data['name']." (".$c.")</strong>";
echo "<br>".getMLText('paperless_view_sort_by').": ".$data['sort_field'];
if ($data['show_on_dashboard']) {
echo "<br>".getMLText('paperless_view_dashboard').': <i class="fa fa-check"></i>';
}
if ($data['show_in_sidebar']) {
echo "<br>".getMLText('paperless_view_sidebar').': <i class="fa fa-check"></i>';
}
echo "</td>";
echo "<td>";
foreach ($data['filter_rules'] as $rule) {
echo getMLText('paperless_view_rule_'.$rule['rule_type']).": ".$this->getFilterValue($rule)."<br>";
}
// echo "<pre>";
// print_r($data);
// echo "</pre>";
echo "</td>";
echo "</tr>";
}
echo "</tbody>\n</table>\n";
} else {
$this->infoMsg(getMLText('upload_area_no_secret_info'));
}
$this->columnEnd();
$this->columnStart(8);
echo '<div id="paperless_view" class="ajax" data-base="ext/paperless/" data-view="PaperlessViews" data-action="list"></div>';
$this->columnEnd();
$this->rowEnd();
$this->contentEnd();
$this->htmlEndPage();
} /* }}} */
}