* @package SeedDMS * @subpackage paperless * @license GPL3 * @copyright Copyright (C) 2023 Uwe Steinmann */ class SeedDMS_ExtPaperless extends SeedDMS_ExtBase { /* {{{ */ /** * Initialization * * Use this method to do some initialization like setting up the hooks * You have access to the following global variables: * $GLOBALS['settings'] : current global configuration * $GLOBALS['settings']->_extensions['example'] : configuration of this extension * $GLOBALS['LANG'] : the language array with translations for all languages * $GLOBALS['SEEDDMS_HOOKS'] : all hooks added so far */ function init() { /* {{{ */ $GLOBALS['SEEDDMS_HOOKS']['initRestAPI'][] = new SeedDMS_ExtPaperless_RestAPI; $GLOBALS['SEEDDMS_HOOKS']['view']['settings'][] = new SeedDMS_ExtPaperless_Settings; $GLOBALS['SEEDDMS_HOOKS']['view']['bootstrap'][] = new SeedDMS_ExtPaperless_Bootstrap; $GLOBALS['SEEDDMS_HOOKS']['view']['style'][] = new SeedDMS_ExtPaperless_Bootstrap; $GLOBALS['SEEDDMS_HOOKS']['view']['usrMgr'][] = new SeedDMS_ExtPaperless_UsrMgr; } /* }}} */ function migrate() { /* {{{ */ $db = $this->dms->getDb(); $settings = $this->settings; $migrated = null; /* check if database table exists */ if (!$db->hasTable('tblPaperlessView')) { $sqlfile = ''; switch($settings->_dbDriver) { case 'mysql': case 'mysqli': case 'mysqlnd': $sqlfile = "paperless.sql"; break; case 'sqlite': $sqlfile = "paperless-sqlite3.sql"; break; case 'pgsql': $sqlfile = "paperless-postgres.sql"; break; } if ($sqlfile) { if ($db->runQueriesFromFile(__DIR__.'/'.$sqlfile)) { return false; } $migrated = true; } } return $migrated; } /* }}} */ function main() { /* {{{ */ } /* }}} */ } /* }}} */ /** * Class containing method for checking the configuration * * @author Uwe Steinmann * @package SeedDMS * @subpackage paperless */ class SeedDMS_ExtPaperless_Settings { /* {{{ */ /** * Hook for checking the configuration * * This hook is not called if the extension isn't enabled */ function checkConfig($view, $extname, $conf) { $settings = $view->getParam('settings'); if($extname != 'paperless') return; if(empty($settings->_extensions['paperless']['jwtsecret']) || empty($settings->_enableFullSearch)) { echo $view->contentSubHeading(getMLText($extname)); if(empty($settings->_extensions['paperless']['jwtsecret'])) echo $view->warningMsg(getMLText('paperless_jwtsecret_not_set')); if(empty($settings->_enableXsendfile)) echo $view->warningMsg(getMLText('paperless_needs_fulltextsearch')); } } } /* }}} */ /** * Class containing methods for hooks when a * * @author Uwe Steinmann * @package SeedDMS * @subpackage paperless */ class SeedDMS_ExtPaperless_Bootstrap { /* {{{ */ function userMenuItems($view, $menuitems) { /* {{{ */ $user = $view->getParam('user'); $settings = $view->getParam('settings'); $accessobject = $view->getParam('accessobject'); if(!$user->isGuest()) { if (((new SeedDMS_Version())->version() < '6.0.0') || !$settings->_advancedAcl || $accessobject->check_view_access('PaperlessViews')) $menuitems['paperless_views'] = array('link'=>$settings->_httpRoot.'ext/paperless/out/out.PaperlessViews.php', 'label'=>getMLText('paperless_views')); } return $menuitems; } /* }}} */ /** * Show the document in a list of documents */ public function documentListRowExtraContent($view, $document, $content) { /* {{{ */ $dms = $view->getParam('dms'); $user = $view->getParam('user'); $settings = $view->getParam('settings'); $conf = $settings->_extensions['paperless']; /* Check if target attribute is configured */ if(empty($conf['correspondentsattr'])) return null; /* Check if target attribute defintion exists */ $docidattrid = (int) $conf['correspondentsattr']; if(!($docidattrdef = $dms->getAttributeDefinition($docidattrid))) return null; $docid = $document->getAttributeValue($docidattrdef); if(isset($extracontent['bottom_title'])) $extracontent['bottom_title'] .= '
'.$docid.''; else $extracontent['bottom_title'] = '
'.$docid.''; return $extracontent; } /* }}} */ } /* }}} */ /** * Class containing methods for hooks when a * * @author Uwe Steinmann * @package SeedDMS * @subpackage paperless */ class SeedDMS_ExtPaperless_UsrMgr { /* {{{ */ function addInfoItem($view, $seluser) { /* {{{ */ $user = $view->getParam('user'); $settings = $view->getParam('settings'); $accessobject = $view->getParam('accessobject'); if(!$seluser->isGuest()) { if(!empty($settings->_extensions['paperless']['jwtsecret'])) { $token = new SeedDMS_JwtToken($settings->_extensions['paperless']['jwtsecret']); if(!empty($settings->_extensions['paperless']['tokenlivetime'])) $days = (int) $settings->_extensions['paperless']['tokenlivetime']; else $days = 365; if(!$tokenstr = $token->jwtEncode($seluser->getId().':'.(time()+$days*84600))) { return null; } return[getMLText('paperless_user_token'), '
'.getMLText('paperless_token_valid_days', ['days'=>$days]).'
']; } else { return null; } } return null; } /* }}} */ } /* }}} */ use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; class SeedDMS_ExtPaperless_Process_Folder { /* {{{ */ protected $list; public function __construct() { $this->list = []; } public function process($folder, $depth=0) { /* {{{ */ // FIXME: hasDocuments() contains also documents not in status=2 $this->list[$folder->getId()] = [$folder->getFolderPathPlain(true, '/'), $folder->hasDocuments()]; } /* }}} */ public function getList() { /* {{{ */ return $this->list; } /* }}} */ } /* }}} */ final class SeedDMS_ExtPaperless_JsonRenderer { /* {{{ */ public function json( ResponseInterface $response, string|array $data = null ): ResponseInterface { $response = $response->withHeader('Content-Type', 'application/json'); if (is_string($data)) { $response->getBody()->write($data); } else { $response->getBody()->write( (string)json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR ) ); } return $response; } } /* }}} */ class SeedDMS_ExtPaperless_RestAPI_Controller { /* {{{ */ protected $container; protected $renderer; static public function mb_word_count($string, $mode = MB_CASE_TITLE, $characters = null) { /* {{{ */ $string = mb_convert_case($string, $mode, "UTF-8"); $addChars = $characters ? preg_quote($characters, '~') : ""; // $regEx = "~[^\p{L}".$addChars."]+~u"; $regEx = "~[^\p{L}".$addChars."]+~u"; return array_count_values(preg_split($regEx,$string, -1, PREG_SPLIT_NO_EMPTY)); } /* }}} */ /** * Get data of document * * @param $document object * @param bool $truncate_content set to true if content shall be truncated * @param bool $full set to true for content from the file instead of the index */ protected function __getDocumentData($document, $truncate_content=false, $full=false) { /* {{{ */ $fulltextservice = $this->container->get('fulltextservice'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $lc = $document->getLatestContent(); $dms = $document->getDMS(); $content = ''; /* The plain text can either be created by the text previewer * or taken from the fulltext index. The text from the fulltext index * does not have stop words anymore if a stop words file was * configured during indexing. */ if($full) { $txtpreviewer = new SeedDMS_Preview_TxtPreviewer($settings->_cacheDir, $settings->_cmdTimeout, $settings->_enableXsendfile); $txtpreviewer->setConversionMgr($conversionmgr); if(!$txtpreviewer->hasPreview($lc)) $txtpreviewer->createPreview($lc); $file = $txtpreviewer->getFileName($lc).".txt"; if(file_exists($file)) $content = file_get_contents($file); } else { if($fulltextservice && $index = $fulltextservice->Indexer()) { $lucenesearch = $fulltextservice->Search(); if($searchhit = $lucenesearch->getDocument($document->getID())) { $idoc = $searchhit->getDocument(); try { if($truncate_content) $content = htmlspecialchars(mb_strimwidth($idoc->getFieldValue('content'), 0, 500, '...')); else $content = htmlspecialchars($idoc->getFieldValue('content')); } catch (Exception $e) { } } } } $cats = $document->getCategories(); $tags = array(); foreach($cats as $cat) $tags[] = (int) $cat->getId(); $correspondent = null; if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { if($attr = $document->getAttribute($attrdef)) { $valueset = $attrdef->getValueSetAsArray(); $i = array_search($attr->getValue(), $valueset); if($i !== false) $correspondent = $i+1; } } $documenttype = null; if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { if($attr = $document->getAttribute($attrdef)) { $valueset = $attrdef->getValueSetAsArray(); $i = array_search($attr->getValue(), $valueset); if($i !== false) $documenttype = $i+1; } } $owner = $document->getOwner(); $data = array( 'id'=>(int)$document->getId(), 'correspondent'=>$correspondent, 'document_type'=>$documenttype, 'storage_path'=>$document->getFolder()->getId(),//null, 'title'=>$document->getName(), 'content'=>$content, 'tags'=>$tags, // 'checksum'=>$lc->getChecksum(), 'created'=>date('Y-m-d\TH:i:s+02:00', $document->getDate()), 'created_date'=>date('Y-m-d', $document->getDate()), 'modified'=>date('Y-m-d\TH:i:s+02:00', $lc->getDate()), 'added'=>date('Y-m-d\TH:i:s+02:00', $document->getDate()), 'deleted_at'=>null, 'archive_serial_number'=> (int) $document->getId(), // was null 'original_file_name'=>$lc->getOriginalFileName(), 'archived_file_name'=>$lc->getOriginalFileName(), 'owner'=>$owner->getId(), 'user_can_change'=>true, 'is_shared_by_requester'=>false, 'notes'=>[], 'custom_fields'=>[], 'page_count'=>1, 'mime_type'=>$lc->getMimeType() ); $attributes = $document->getAttributes(); if($attributes) { foreach($attributes as $attribute) { $attrdef = $attribute->getAttributeDefinition(); /* FIXME: do not add correspondents and document_types */ if (!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef->getId() == $settings->_extensions['paperless']['documenttypesattr']) continue; if (!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef->getId() == $settings->_extensions['paperless']['correspondentsattr']) continue; $data['custom_fields'][] = [ 'field'=>$attrdef->getId(), 'value'=>$attribute->getValueAsString() ]; } } if ($comment = $document->getComment()) { $data['notes'][] = [ 'id'=>$lc->getId(), 'note'=>$document->getComment(), 'created'=>date('Y-m-d\TH:i:s+02:00', $lc->getDate()), "user"=>[ 'id'=>$owner->getId(), 'username'=>$owner->getLogin(), 'first_name'=>"", 'last_name'=>$owner->getFullName() ] ]; } return $data; } /* }}} */ /** * Get data of user * * @param $user object * @param bool $truncate_content set to true if content shall be truncated * @param bool $full set to true for content from the file instead of the index */ protected function __getUserData($user) { /* {{{ */ $data = array( 'id'=>(int)$user->getId(), 'username'=>$user->getLogin(), 'email'=>$user->getEmail(), 'first_name'=>'', 'last_name'=>$user->getFullName(), 'is_staff'=>$user->isUser() || $user->isAdmin(), 'is_active'=>!$user->isDisabled(), 'is_superuser'=>$user->isAdmin(), 'groups'=>[], 'user_permissions'=>[], 'inherited_permissions'=>[ 'change_customfield', 'add_emailaddress', 'delete_chordcounter', 'view_schedule', 'change_contenttype', 'delete_logentry', 'delete_group', 'view_chordcounter', 'add_tag', 'add_savedviewfilterrule', 'view_groupobjectpermission', 'view_socialtoken', 'add_socialtoken', 'change_uisettings', 'change_groupobjectpermission', 'change_document', 'add_note', 'add_authenticator', 'delete_socialtoken', 'change_logentry', 'add_tokenproxy', 'view_failure', 'add_documenttype', 'view_tokenproxy', 'change_authenticator', 'delete_task', 'change_task', 'delete_savedview', 'view_document', 'delete_userobjectpermission', 'delete_schedule', 'change_mailrule', 'add_chordcounter', 'delete_workflowactionwebhook', 'delete_mailaccount', 'view_success', 'change_note', 'add_workflowtrigger', 'view_group', 'add_customfield', 'view_savedview', 'delete_correspondent', 'delete_token', 'view_tag', 'delete_note', 'change_savedviewfilterrule', 'view_applicationconfiguration', 'delete_socialaccount', 'delete_mailrule', 'add_user', 'view_workflowtrigger', 'delete_processedmail', 'add_sharelink', 'delete_workflowrun', 'delete_groupobjectpermission', 'add_uisettings', 'add_workflowaction', 'delete_log', 'delete_permission', 'change_permission', 'delete_emailaddress', 'view_workflowaction', 'change_tokenproxy', 'change_correspondent', 'add_failure', 'change_workflowrun', 'delete_document', 'add_correspondent', 'delete_savedviewfilterrule', 'add_processedmail', 'add_socialapp', 'view_logentry', 'view_socialaccount', 'view_workflowactionwebhook', 'add_socialaccount', 'view_documenttype', 'change_log', 'delete_paperlesstask', 'add_permission', 'delete_workflowactionemail', 'change_success', 'change_chordcounter', 'change_workflowactionwebhook', 'change_failure', 'add_logentry', 'delete_ormq', 'change_socialtoken', 'change_userobjectpermission', 'change_applicationconfiguration', 'view_processedmail', 'change_emailaddress', 'view_comment', 'change_savedview', 'view_note', 'add_document', 'delete_customfieldinstance', 'delete_applicationconfiguration', 'add_ormq', 'add_logentry', 'view_task', 'view_authenticator', 'view_savedviewfilterrule', 'add_schedule', 'add_savedview', 'change_socialapp', 'add_groupresult', 'change_storagepath', 'add_userobjectpermission', 'delete_socialapp', 'change_workflow', 'view_emailconfirmation', 'view_mailrule', 'view_token', 'change_socialaccount', 'view_ormq', 'add_mailaccount', 'delete_storagepath', 'view_correspondent', 'change_mailaccount', 'add_group', 'change_tag', 'add_success', 'view_permission', 'delete_comment', 'delete_workflowtrigger', 'view_session', 'delete_logentry', 'add_emailconfirmation', 'add_taskresult', 'delete_failure', 'add_token', 'view_contenttype', 'add_workflowactionemail', 'view_customfield', 'change_group', 'add_log', 'delete_uisettings', 'delete_customfield', 'change_processedmail', 'change_user', 'add_workflow', 'change_emailconfirmation', 'delete_authenticator', 'delete_sharelink', 'view_paperlesstask', 'delete_taskresult', 'view_user', 'add_groupobjectpermission', 'view_storagepath', 'view_groupresult', 'view_customfieldinstance', 'change_ormq', 'change_workflowactionemail', 'view_sharelink', 'change_workflowtrigger', 'change_documenttype', 'add_comment', 'view_taskresult', 'delete_groupresult', 'add_storagepath', 'change_logentry', 'view_socialapp', 'add_workflowactionwebhook', 'view_workflowactionemail', 'change_session', 'change_paperlesstask', 'view_log', 'view_userobjectpermission', 'delete_session', 'view_uisettings', 'change_comment', 'delete_contenttype', 'delete_workflow', 'add_session', 'delete_user', 'delete_workflowaction', 'add_workflowrun', 'delete_success', 'view_logentry', 'change_taskresult', 'add_customfieldinstance', 'add_applicationconfiguration', 'change_schedule', 'add_contenttype', 'view_mailaccount', 'change_token', 'change_groupresult', 'add_mailrule', 'add_paperlesstask', 'view_workflowrun', 'add_task', 'view_workflow', 'delete_emailconfirmation', 'change_sharelink', 'change_customfieldinstance', 'view_emailaddress', 'delete_tag', 'delete_tokenproxy', 'change_workflowaction', 'delete_documenttype' ], 'is_mfa_enabled'=>false ); return $data; } /* }}} */ public function getContrastColor($hexcolor) { /* {{{ */ $r = hexdec(substr($hexcolor, 1, 2)); $g = hexdec(substr($hexcolor, 3, 2)); $b = hexdec(substr($hexcolor, 5, 2)); $yiq = (($r * 299) + ($g * 587) + ($b * 114)) / 1000; return ($yiq >= 148) ? '000000' : 'ffffff'; } /* }}} */ protected function __getCategoryData($category, $inboxtags) { /* {{{ */ $color = substr(md5($category->getName()), 0, 6); $data = [ 'id'=>(int)$category->getId(), 'slug'=>strtolower($category->getName()), 'name'=>$category->getName(), 'color'=>'#'.$color, //'#50b02c', 'text_color'=>'#'.$this->getContrastColor($color), 'match'=>'', 'matching_algorithm'=>6, 'is_insensitive'=>true, 'is_inbox_tag'=>in_array($category->getId(), $inboxtags), 'document_count'=>0 ]; return $data; } /* }}} */ protected function __getAttributeDefinitionData($attrdef) { /* {{{ */ $typemap = ['0'=>'??', '1'=>'int', '2'=>'float', '3'=>'string', '4'=>'boolean', '5'=>'url', '6'=>'email', '7'=>'date', '101'=>'folder', '102'=>'document', '104'=>'user', '105'=>'group']; $data = [ 'id'=>(int)$attrdef->getId(), 'name'=>$attrdef->getName(), 'data_type'=>$typemap[$attrdef->getType()], 'document_count'=>0 ]; return $data; } /* }}} */ // constructor receives container instance public function __construct(ContainerInterface $container, SeedDMS_ExtPaperless_JsonRenderer $renderer) { /* {{{ */ $this->container = $container; $this->renderer = $renderer; } /* }}} */ function api($request, $response) { /* {{{ */ $data = array( 'correspondents'=>$request->getUri().'correspondents/', 'document_types'=>$request->getUri().'document_types/', 'documents'=>$request->getUri().'documents/', 'logs'=>$request->getUri().'logs/', 'tags'=>$request->getUri().'tags/', 'saved_views'=>$request->getUri().'saved_views/', 'storage_paths'=>$request->getUri().'storage_paths/', 'tasks'=>$request->getUri().'tasks/', 'mail_accounts'=>$request->getUri().'mail_accounts/', 'mail_rule'=>$request->getUri().'mail_rule/', ); return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ /** * Currently returns the seeddms version but it may be better to * return a faked paperless version. */ function remote_version($request, $response) { /* {{{ */ $v = new \Seeddms\Seeddms\Version(); $data = array( 'version' => $v->version(), 'update_available'=>false ); return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ /** * Currently returns the seeddms version but it may be better to * return a faked paperless version. */ function schema($request, $response) { /* {{{ */ return $response->withStatus(200); } /* }}} */ function token($request, $response) { /* {{{ */ $settings = $this->container->get('config'); $authenticator = $this->container->get('authenticator'); $logger = $this->container->get('logger'); $data = $request->getParsedBody(); if(empty($data['username'])) { $body = $request->getBody(); $data = json_decode($body, true); } if($data) { $userobj = $authenticator->authenticate($data['username'], $data['password']); if(!$userobj) return $this->renderer->json($response, array('non_field_errors'=>['Unable to log in with provided credentials.']))->withStatus(403); else { if(!empty($settings->_extensions['paperless']['jwtsecret'])) { $token = new SeedDMS_JwtToken($settings->_extensions['paperless']['jwtsecret']); if(!empty($settings->_extensions['paperless']['tokenlivetime'])) $days = (int) $settings->_extensions['paperless']['tokenlivetime']; else $days = 365; if(!$tokenstr = $token->jwtEncode($userobj->getId().':'.(time()+$days*84600))) { return $response->withStatus(403); } return $this->renderer->json($response, array('token'=>$tokenstr))->withStatus(200); } else { return $this->renderer->json($response, array('token'=>$settings->_apiKey))->withStatus(200); } } } return $response->withStatus(403); } /* }}} */ function profile($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!$userobj) return $response->withStatus(403); $data = []; $data['email'] = $userobj->getEmail(); $data['password'] = ''; $tmp = explode(' ', $userobj->getFullName(), 2); $data['first_name'] = $tmp[0]; $data['last_name'] = isset($tmp[1]) ? $tmp[1] : ''; $data['auth_token'] = ''; $data['social_account'] = []; $data['has_usable_password'] = true; $data['is_mfa_enabled'] = false; return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ function patch_profile($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!$userobj) return $response->withStatus(403); $data = $request->getParsedBody(); $logger->log(var_export($data, true), PEAR_LOG_DEBUG); if (isset($data['email'])) { $userobj->setEmail($data['email']); } if (isset($data['first_name']) || isset($data['last_name'])) { $userobj->setFullName(trim($data['first_name'].' '.$data['last_name'])); } if (isset($data['password'])) { $userobj->setPwd(seed_pass_hash($data['password'])); } $data = []; $data['email'] = $userobj->getEmail(); $data['password'] = ''; $tmp = explode(' ', $userobj->getFullName(), 2); $data['first_name'] = $tmp[0]; $data['last_name'] = isset($tmp[1]) ? $tmp[1] : ''; $data['auth_token'] = ''; $data['social_account'] = []; $data['has_usable_password'] = true; $data['is_mfa_enabled'] = false; return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ function tags($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if(false === ($categories = $dms->getDocumentCategories())) { return $this->renderer->json($response, array('results'=>null))->withStatus(500); } if(!empty($settings->_extensions['paperless']['usehomefolder'])) { if(!($startfolder = $dms->getFolder((int) $userobj->getHomeFolder()))) $startfolder = $dms->getFolder($settings->_rootFolderID); } elseif(!isset($settings->_extensions['paperless']['rootfolder']) || !($startfolder = $dms->getFolder($settings->_extensions['paperless']['rootfolder']))) $startfolder = $dms->getFolder($settings->_rootFolderID); if($fulltextservice && $index = $fulltextservice->Indexer()) { $lucenesearch = $fulltextservice->Search(); $searchresult = $lucenesearch->search('', array('record_type'=>['document'], 'status'=>[2], 'user'=>[$userobj->getLogin()], 'startFolder'=>$startfolder, 'rootFolder'=>$startfolder), array('limit'=>1), array(), array('no_facets'=>false)); if($searchresult === false) { return $response->withStatus(500); } else { $recs = array(); $facets = $searchresult['facets']; } } $params = $request->getQueryParams(); $logger->log(var_export($params, true), PEAR_LOG_DEBUG); $data = []; $inboxtags = []; if(!empty($settings->_extensions['paperless']['inboxtags'])) $inboxtags = explode(',', $settings->_extensions['paperless']['inboxtags']); foreach($categories as $category) { /* Check if category meets search criteria in query params */ if (!empty($params['id']) && $category->getId() != $params['id']) { continue; } if (!empty($params['id__in'])) { $catids = explode(',', $params['id__in']); if (!in_array($category->getId(), $catids)) continue; } if (!empty($params['name__icontains']) && (false === stristr($category->getName(), $params['name__icontains']))) { continue; } if (!empty($params['name__istartswith']) && (!str_starts_with($category->getName(), $params['name__istartswith']))) { continue; } if (!empty($params['name__iendswith']) && (!str_ends_with($category->getName(), $params['name__iendswith']))) { continue; } if (!empty($params['name__iexact']) && ($category->getName() !== $params['name__iexact'])) { continue; } $tmp = $this->__getCategoryData($category, $inboxtags); if(isset($facets['category'][$category->getName()])) $tmp['document_count'] = (int) $facets['category'][$category->getName()]; $data[] = $tmp; } return $this->renderer->json($response, array('count'=>count($data), 'next'=>null, 'previous'=>null, 'results'=>$data))->withStatus(200); } /* }}} */ function tag($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $id = ((int) $args['id']); $category = $dms->getDocumentCategory($id); if (!$category) return $this->renderer->json($response, array('detail'=>'No Tag matches the given query.'))->withStatus(404); if(!empty($settings->_extensions['paperless']['usehomefolder'])) { if(!($startfolder = $dms->getFolder((int) $userobj->getHomeFolder()))) $startfolder = $dms->getFolder($settings->_rootFolderID); } elseif(!isset($settings->_extensions['paperless']['rootfolder']) || !($startfolder = $dms->getFolder($settings->_extensions['paperless']['rootfolder']))) $startfolder = $dms->getFolder($settings->_rootFolderID); if($fulltextservice && $index = $fulltextservice->Indexer()) { $lucenesearch = $fulltextservice->Search(); $searchresult = $lucenesearch->search('', array('record_type'=>['document'], 'status'=>[2], 'user'=>[$userobj->getLogin()], 'startFolder'=>$startfolder, 'rootFolder'=>$startfolder, 'category'=>[$category->getName()]), array('limit'=>20), array()); if($searchresult === false) { return $response->withStatus(500); } else { $recs = array(); $facets = $searchresult['facets']; } } $inboxtags = []; if(!empty($settings->_extensions['paperless']['inboxtags'])) $inboxtags = explode(',', $settings->_extensions['paperless']['inboxtags']); $tmp = $this->__getCategoryData($category, $inboxtags); if(isset($facets['category'][$category->getName()])) $tmp['document_count'] = (int) $facets['category'][$category->getName()]; return $this->renderer->json($response, $tmp)->withStatus(200); } /* }}} */ function post_tag($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $fulltextservice = $this->container->get('fulltextservice'); $notifier = $this->container->get('notifier'); if(!$userobj->isAdmin()) return $response->withStatus(404); $data = $request->getParsedBody(); $oldcat = $dms->getDocumentCategoryByName($data['name']); if (is_object($oldcat)) { return $this->renderer->json($response, getMLText('paperless_tag_already_exists'))->withStatus(400); } $newCategory = $dms->addDocumentCategory($data['name']); if (!$newCategory) return $this->renderer->json($response, getMLText('paperless_could_not_create_tag'))->withStatus(400); return $this->renderer->json($response, $this->__getCategoryData($newCategory, []))->withStatus(201); } /* }}} */ function patch_tag($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $id = ((int) $args['id']); $category = $dms->getDocumentCategory($id); if (!$category) return $this->renderer->json($response, array('detail'=>'No Tag matches the given query.'))->withStatus(404); $data = $request->getParsedBody(); $category->setName($data['name']); $inboxtags = []; if(!empty($settings->_extensions['paperless']['inboxtags'])) $inboxtags = explode(',', $settings->_extensions['paperless']['inboxtags']); $tmp = $this->__getCategoryData($category, $inboxtags); // if(isset($facets['category'][$category->getName()])) // $tmp['document_count'] = (int) $facets['category'][$category->getName()]; return $this->renderer->json($response, $tmp)->withStatus(200); } /* }}} */ function delete_tag($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); $notifier = $this->container->get('notifier'); $fulltextservice = $this->container->get('fulltextservice'); if(!$userobj->isAdmin()) return $response->withStatus(404); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $cat = $dms->getDocumentCategory($args['id']); if($cat) { $documents = $cat->getDocumentsByCategory(10); if($documents) { $logger->log('Will not remove because cat has documents', PEAR_LOG_WARNING); return $response->withStatus(400); } else { $logger->log('remove categorie', PEAR_LOG_INFO); $cat->remove(); } } return $response->withStatus(204); } /* }}} */ function custom_fields($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if(false === ($attrdefs = $dms->getAllAttributeDefinitions([SeedDMS_Core_AttributeDefinition::objtype_all, SeedDMS_Core_AttributeDefinition::objtype_document]))) { return $this->renderer->json($response, array('results'=>null))->withStatus(500); } if(!empty($settings->_extensions['paperless']['usehomefolder'])) { if(!($startfolder = $dms->getFolder((int) $userobj->getHomeFolder()))) $startfolder = $dms->getFolder($settings->_rootFolderID); } elseif(!isset($settings->_extensions['paperless']['rootfolder']) || !($startfolder = $dms->getFolder($settings->_extensions['paperless']['rootfolder']))) $startfolder = $dms->getFolder($settings->_rootFolderID); if($fulltextservice && $index = $fulltextservice->Indexer()) { $lucenesearch = $fulltextservice->Search(); $searchresult = $lucenesearch->search('', array('record_type'=>['document'], 'status'=>[2], 'user'=>[$userobj->getLogin()], 'startFolder'=>$startfolder, 'rootFolder'=>$startfolder), array('limit'=>20), array()); if($searchresult === false) { return $response->withStatus(500); } else { $recs = array(); $facets = $searchresult['facets']; //print_r($facets); } } $data = []; foreach($attrdefs as $attrdef) { if (!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef->getId() == $settings->_extensions['paperless']['documenttypesattr']) continue; if (!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef->getId() == $settings->_extensions['paperless']['correspondentsattr']) continue; $tmp = $this->__getAttributeDefinitionData($attrdef); if(isset($facets['attr_'.$attrdef->getId()])) $tmp['document_count'] = array_sum($facets['attr_'.$attrdef->getId()]); $data[] = $tmp; } return $this->renderer->json($response, array('count'=>count($data), 'next'=>null, 'previous'=>null, 'results'=>$data))->withStatus(200); } /* }}} */ function users($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $data = []; $users = $dms->getAllUsers(); foreach($users as $user) { $data[] = $this->__getUserData($user); } return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ function user($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $user = $dms->getUser($args['id']); if($user) { $rec = $this->__getUserData($user); return $this->renderer->json($response, $rec)->withStatus(200); } else { return $response->withStatus(404); } return $response->withStatus(500); } /* }}} */ /* FIXME: This method does not take the document status into account * It might be better to create a facet from the correspondent field * instead of calling getStatistics() */ function correspondents($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $params = $request->getQueryParams(); $logger->log(var_export($params, true), PEAR_LOG_DEBUG); $correspondents = array(); if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { $res = $attrdef->getStatistics(30); // print_r($res['frequencies']); $valueset = $attrdef->getValueSetAsArray(); foreach($valueset as $id=>$val) { /* Check if correspondent meets search criteria in query params */ if (!empty($params['id']) && ($id+1 != $params['id'])) { continue; } if (!empty($params['id__in'])) { $ids = explode(',', $params['id__in']); if (!in_array($id+1, $ids)) continue; } if (!empty($params['name__icontains']) && (false === stristr($val, $params['name__icontains']))) { continue; } if (!empty($params['name__istartswith']) && (!str_starts_with($val, $params['name__istartswith']))) { continue; } if (!empty($params['name__iendswith']) && (!str_ends_with($val, $params['name__iendswith']))) { continue; } if (!empty($params['name__iexact']) && ($val !== $params['name__iexact'])) { continue; } $c = isset($res['frequencies']['document'][md5($val)]) ? $res['frequencies']['document'][md5($val)]['c'] : 0; $correspondents[] = array( 'id'=>$id+1, 'slug'=>strtolower($val), 'name'=>$val, 'match'=>'', 'matching_algorithm'=>1, 'is_insensitive'=>true, 'document_count'=>$c, 'last_correspondence'=>null, 'owner'=>null, 'user_can_change'=>true ); } } return $this->renderer->json($response, array('count'=>count($correspondents), 'next'=>null, 'previous'=>null, 'results'=>$correspondents))->withStatus(200); } /* }}} */ function correspondent($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $id = ((int) $args['id']) - 1; if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { $res = $attrdef->getStatistics(30); //print_r($res['frequencies']); $valueset = $attrdef->getValueSetAsArray(); if (isset($valueset[$id])) { $c = isset($res['frequencies']['document'][md5($valueset[$id])]) ? $res['frequencies']['document'][md5($valueset[$id])]['c'] : 0; $correspondent = array( 'id'=>$id+1, 'slug'=>strtolower($valueset[$id]), 'name'=>$valueset[$id], 'match'=>'', 'matching_algorithm'=>1, 'is_insensitive'=>true, 'document_count'=>$c, 'last_correspondence'=>null, 'owner'=>null, 'user_can_change'=>true ); return $this->renderer->json($response, $correspondent)->withStatus(200); } } return $this->renderer->json($response, array('detail'=>'No Correspondent matches the given query.'))->withStatus(404); } /* }}} */ function post_correspondents($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $fulltextservice = $this->container->get('fulltextservice'); $notifier = $this->container->get('notifier'); if(!$userobj->isAdmin()) return $response->withStatus(404); $data = $request->getParsedBody(); if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { $valueset = $attrdef->getValueSetAsArray(); if (false !== array_search($data['name'], $valueset)) return $this->renderer->json($response, getMLText('paperless_correspondent_already_exists'))->withStatus(400); $valueset[] = $data['name']; if (!$attrdef->setValueSet('|'.implode('|', $valueset))) { return $this->renderer->json($response, getMLText('paperless_could_not_create_tag'))->withStatus(400); } $correspondent = array( 'id'=>count($valueset), 'slug'=>strtolower($data['name']), 'name'=>$data['name'], 'match'=>'', 'matching_algorithm'=>1, 'is_insensitive'=>true, 'document_count'=>0, 'last_correspondence'=>null, 'owner'=>null, 'user_can_change'=>true ); return $this->renderer->json($response, $correspondent)->withStatus(201); } return $this->renderer->json($response, getMLText('paperless_could_not_create_correspondent'))->withStatus(400); } /* }}} */ /** * called by PATCH and PUT /api/correspondents/:id */ function patch_correspondent($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $id = ((int) $args['id']) - 1; if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { $res = $attrdef->getStatistics(30); // $rawBody = (string) $request->getBody(); if (!($data = $request->getParsedBody())) { return $response->withStatus(404); } $logger->log(var_export($data, true), PEAR_LOG_DEBUG); $valueset = $attrdef->getValueSetAsArray(); if (isset($valueset[$id])) { $oldval = $valueset[$id]; $c = isset($res['frequencies']['document'][md5($oldval)]) ? $res['frequencies']['document'][md5($oldval)]['c'] : 0; if ($oldval !== $data['name']) { /* Update value set */ $valueset[$id] = $data['name']; if (!$attrdef->setValueSet('|'.implode('|', $valueset))) { return $this->renderer->json($response, getMLText('paperless_could_not_set_correspondent'))->withStatus(400); } else { /* Update all documents with new value */ if (!$attrdef->updateValue($oldval, $data['name'])) { return $this->renderer->json($response, getMLText('paperless_could_not_update_correspondent'))->withStatus(400); } } } $correspondent = array( 'id'=>$id+1, 'slug'=>strtolower($data['name']), 'name'=>$data['name'], 'match'=>'', 'matching_algorithm'=>1, 'is_insensitive'=>true, 'document_count'=>$c, 'owner'=>null, 'user_can_change'=>true ); return $this->renderer->json($response, $correspondent)->withStatus(200); } } return $this->renderer->json($response, array('detail'=>'No Correspondent matches the given query.'))->withStatus(404); } /* }}} */ function delete_correspondent($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $id = ((int) $args['id']) - 1; if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { $res = $attrdef->getStatistics(30); $valueset = $attrdef->getValueSetAsArray(); if (isset($valueset[$id])) { $val = $valueset[$id]; $c = isset($res['frequencies']['document'][md5($val)]) ? $res['frequencies']['document'][md5($val)]['c'] : 0; if ($c == 0) { unset($valueset[$id]); if (!$attrdef->setValueSet('|'.implode('|', $valueset))) { return $this->renderer->json($response, getMLText('paperless_could_not_set_correspondent'))->withStatus(400); } else { if (!$attrdef->removeValue($oldval)) { return $this->renderer->json($response, getMLText('paperless_could_not_update_correspondent'))->withStatus(400); } } return $response->withStatus(204); } else { return $this->renderer->json($response, array('detail'=>'Correspondent is still used by '.$c.' documents.'))->withStatus(404); } } } return $this->renderer->json($response, array('detail'=>'No Correspondent matches the given query.'))->withStatus(404); } /* }}} */ function document_types($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $params = $request->getQueryParams(); $logger->log(var_export($params, true), PEAR_LOG_DEBUG); $types = array(); if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { $res = $attrdef->getStatistics(30); $valueset = $attrdef->getValueSetAsArray(); foreach($valueset as $id=>$val) { /* Check if document type meets search criteria in query params */ if (!empty($params['id']) && ($id+1 != $params['id'])) { continue; } if (!empty($params['id__in'])) { $ids = explode(',', $params['id__in']); if (!in_array($id+1, $ids)) continue; } if (!empty($params['name__icontains']) && (false === stristr($val, $params['name__icontains']))) { continue; } if (!empty($params['name__istartswith']) && (!str_starts_with($val, $params['name__istartswith']))) { continue; } if (!empty($params['name__iendswith']) && (!str_ends_with($val, $params['name__iendswith']))) { continue; } if (!empty($params['name__iexact']) && ($val !== $params['name__iexact'])) { continue; } $c = isset($res['frequencies']['document'][md5($val)]) ? $res['frequencies']['document'][md5($val)]['c'] : 0; $types[] = array( 'id'=>$id+1, 'slug'=>strtolower($val), 'name'=>$val, 'match'=>'', 'matching_algorithm'=>1, 'is_insensitive'=>true, 'document_count'=>$c ); } } return $this->renderer->json($response, array('count'=>count($types), 'next'=>null, 'previous'=>null, 'results'=>$types))->withStatus(200); } /* }}} */ function document_type($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $id = ((int) $args['id']) - 1; if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { $res = $attrdef->getStatistics(30); //print_r($res['frequencies']); $valueset = $attrdef->getValueSetAsArray(); if (isset($valueset[$id])) { $c = isset($res['frequencies']['document'][md5($valueset[$id])]) ? $res['frequencies']['document'][md5($valueset[$id])]['c'] : 0; $documenttype = array( 'id'=>$id+1, 'slug'=>strtolower($valueset[$id]), 'name'=>$valueset[$id], 'match'=>'', 'matching_algorithm'=>1, 'is_insensitive'=>true, 'document_count'=>$c, 'last_correspondence'=>null, 'owner'=>null, 'user_can_change'=>true ); return $this->renderer->json($response, $documenttype)->withStatus(200); } } return $this->renderer->json($response, array('detail'=>'No Correspondent matches the given query.'))->withStatus(404); } /* }}} */ function post_document_types($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $fulltextservice = $this->container->get('fulltextservice'); $notifier = $this->container->get('notifier'); if(!$userobj->isAdmin()) return $response->withStatus(404); $data = $request->getParsedBody(); if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { $valueset = $attrdef->getValueSetAsArray(); if (false !== array_search($data['name'], $valueset)) return $this->renderer->json($response, getMLText('paperless_document_type_already_exists'))->withStatus(400); $valueset[] = $data['name']; if (!$attrdef->setValueSet('|'.implode('|', $valueset))) { return $this->renderer->json($response, getMLText('paperless_could_not_create_tag'))->withStatus(400); } $documenttype = array( 'id'=>count($valueset), 'slug'=>strtolower($data['name']), 'name'=>$data['name'], 'match'=>'', 'matching_algorithm'=>1, 'is_insensitive'=>true, 'document_count'=>0, 'owner'=>null, 'user_can_change'=>true ); return $this->renderer->json($response, $documenttype)->withStatus(201); } return $this->renderer->json($response, getMLText('paperless_could_not_create_document_type'))->withStatus(400); } /* }}} */ /** * called by PATCH and PUT /api/document_types/:id */ function patch_document_type($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $id = ((int) $args['id']) - 1; if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { $res = $attrdef->getStatistics(30); // $rawBody = (string) $request->getBody(); if (!($data = $request->getParsedBody())) { return $response->withStatus(404); } $logger->log(var_export($data, true), PEAR_LOG_DEBUG); $valueset = $attrdef->getValueSetAsArray(); if (isset($valueset[$id])) { $oldval = $valueset[$id]; $c = isset($res['frequencies']['document'][md5($oldval)]) ? $res['frequencies']['document'][md5($oldval)]['c'] : 0; if ($oldval !== $data['name']) { /* Update value set */ $valueset[$id] = $data['name']; if (!$attrdef->setValueSet('|'.implode('|', $valueset))) { return $this->renderer->json($response, getMLText('paperless_could_not_set_document_type'))->withStatus(400); } else { /* Update all documents with new value */ if (!$attrdef->updateValue($oldval, $data['name'])) { return $this->renderer->json($response, getMLText('paperless_could_not_update_document_type'))->withStatus(400); } } } $documenttype = array( 'id'=>$id+1, 'slug'=>strtolower($data['name']), 'name'=>$data['name'], 'match'=>'', 'matching_algorithm'=>1, 'is_insensitive'=>true, 'document_count'=>$c, 'owner'=>null, 'user_can_change'=>true ); return $this->renderer->json($response, $documenttype)->withStatus(200); } } return $this->renderer->json($response, array('detail'=>'No Correspondent matches the given query.'))->withStatus(404); } /* }}} */ function delete_document_type($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $id = ((int) $args['id']) - 1; if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { $res = $attrdef->getStatistics(30); $valueset = $attrdef->getValueSetAsArray(); if (isset($valueset[$id])) { $val = $valueset[$id]; $c = isset($res['frequencies']['document'][md5($val)]) ? $res['frequencies']['document'][md5($val)]['c'] : 0; if ($c == 0) { unset($valueset[$id]); if (!$attrdef->setValueSet('|'.implode('|', $valueset))) { return $this->renderer->json($response, getMLText('paperless_could_not_set_document_type'))->withStatus(400); } else { if (!$attrdef->removeValue($oldval)) { return $this->renderer->json($response, getMLText('paperless_could_not_update_document_type'))->withStatus(400); } } return $response->withStatus(204); } else { return $this->renderer->json($response, array('detail'=>'Correspondent is still used by '.$c.' documents.'))->withStatus(404); } } } return $this->renderer->json($response, array('detail'=>'No Correspondent matches the given query.'))->withStatus(404); } /* }}} */ function __custom_fields($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $customfields = array(); $attrdefs = $dms->getAllAttributeDefinitions([SeedDMS_Core_AttributeDefinition::objtype_all, SeedDMS_Core_AttributeDefinition::objtype_document]); $typemap = ['0'=>'??', '1'=>'int', '2'=>'float', '3'=>'string', '4'=>'boolean', '5'=>'url', '6'=>'email', '7'=>'date', '101'=>'folder', '102'=>'document', '104'=>'user', '105'=>'group']; foreach($attrdefs as $attrdef) { if (in_array($attrdef->getId(), [$settings->_extensions['paperless']['documenttypesattr'], $settings->_extensions['paperless']['correspondentsattr']])) continue; $customfields[] = array( 'id'=>$attrdef->getId(), 'name'=>$attrdef->getName(), 'data_type'=>$typemap[$attrdef->getType()], ); } return $this->renderer->json($response, array('count'=>count($customfields), 'next'=>null, 'previous'=>null, 'results'=>$customfields))->withStatus(200); } /* }}} */ function saved_views($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); require_once('class.PaperlessView.php'); $views = SeedDMS_PaperlessView::getAllInstances($userobj, $dms); $data = []; foreach($views as $view) { $tmp = $view->getView(); $data[] = $tmp; } return $this->renderer->json($response, array('count'=>count($data), 'next'=>null, 'previous'=>null, 'results'=>$data))->withStatus(200); } /* }}} */ function post_saved_views($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $fulltextservice = $this->container->get('fulltextservice'); $notifier = $this->container->get('notifier'); require_once('class.PaperlessView.php'); $data = $request->getParsedBody(); $logger->log(var_export($data, true), PEAR_LOG_DEBUG); $view = new SeedDMS_PaperlessView(0, $userobj, $data); $view->setDMS($dms); if($newview = $view->save()) { // $logger->log(var_export($newview, true), PEAR_LOG_DEBUG); return $this->renderer->json($response, $newview->getView())->withStatus(201); } else { return $response->withStatus(501); } } /* }}} */ function delete_saved_views($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); $notifier = $this->container->get('notifier'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); require_once('class.PaperlessView.php'); $view = SeedDMS_PaperlessView::getInstance($args['id'], $dms); if($view) { $logger->log("remove saved view '".$view->getView()['name']."'", PEAR_LOG_INFO); $view->remove(); } return $response->withStatus(204);//->withBody('No Content'); } /* }}} */ function patch_saved_views($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); $notifier = $this->container->get('notifier'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); require_once('class.PaperlessView.php'); $data = $request->getParsedBody(); $logger->log(var_export($data, true), PEAR_LOG_DEBUG); $view = SeedDMS_PaperlessView::getInstance($args['id'], $dms); if($view) { $logger->log('patch saved view', PEAR_LOG_INFO); $view->setView($data); $view->save(); } return $this->renderer->json($response, $view->getView())->withStatus(200); } /* }}} */ function storage_paths($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); if(!empty($settings->_extensions['paperless']['usehomefolder'])) { if(!($rootfolder = $dms->getFolder((int) $userobj->getHomeFolder()))) $rootfolder = $dms->getFolder($settings->_rootFolderID); } elseif(!isset($settings->_extensions['paperless']['rootfolder']) || !($rootfolder = $dms->getFolder($settings->_extensions['paperless']['rootfolder']))) $rootfolder = $dms->getFolder($settings->_rootFolderID); $folderprocess = new SeedDMS_ExtPaperless_Process_Folder(); // call_user_func(array($folderprocess, 'process'), $folder, -1); $tree = new SeedDMS_FolderTree($rootfolder, array($folderprocess, 'process')); $list = $folderprocess->getList(); $paths = array(); foreach($list as $fid=>$path) { if($path[1] > 0) $paths[] = array('id'=>(int)$fid, 'name'=>$path[0], 'slug'=>$path[0], 'path'=>$path[0], 'match'=>'', 'matching_algorithm'=>6, 'is_insensitive'=>true, 'document_count'=>$path[1]); } return $this->renderer->json($response, array('count'=>count($paths), 'next'=>null, 'previous'=>null, 'results'=>$paths))->withStatus(200); } /* }}} */ function storage_path($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); if(!empty($settings->_extensions['paperless']['usehomefolder'])) { if(!($rootfolder = $dms->getFolder((int) $userobj->getHomeFolder()))) $rootfolder = $dms->getFolder($settings->_rootFolderID); } elseif(!isset($settings->_extensions['paperless']['rootfolder']) || !($rootfolder = $dms->getFolder($settings->_extensions['paperless']['rootfolder']))) $rootfolder = $dms->getFolder($settings->_rootFolderID); if ($folder = $dms->getFolder((int) $args['id'])) { $c = $folder->hasDocuments(); if($c > 0) $p = $folder->getFolderPathPlain(true, '/'); $path = array('id'=>$folder->getId(), 'name'=>$p, 'slug'=>$p, 'path'=>$p, 'match'=>'', 'matching_algorithm'=>6, 'is_insensitive'=>true, 'document_count'=>$c); return $this->renderer->json($response, $path)->withStatus(200); } return $this->renderer->json($response, array('detail'=>'No storage path matches the given query.'))->withStatus(404); } /* }}} */ function documents($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); $params = $request->getQueryParams(); $logger->log(var_export($params, true), PEAR_LOG_DEBUG); if(!empty($settings->_extensions['paperless']['usehomefolder'])) { if(!($rootfolder = $dms->getFolder((int) $userobj->getHomeFolder()))) $rootfolder = $dms->getFolder($settings->_rootFolderID); } elseif(!isset($settings->_extensions['paperless']['rootfolder']) || !($rootfolder = $dms->getFolder($settings->_extensions['paperless']['rootfolder']))) $rootfolder = $dms->getFolder($settings->_rootFolderID); $startfolder = $rootfolder; $logger->log('Searching for documents in folder '.$rootfolder->getId(), PEAR_LOG_DEBUG); $fullsearch = true; $query = ''; $astart = $mstart = 0; $aend = $mend = 0; if($fullsearch) { if (isset($params["query"]) && is_string($params["query"])) { $queryparts = explode(',', $params["query"]); foreach($queryparts as $querypart) { /* 'added' is time when a document was added. This is 'created' * in the fulltext index. */ if(substr($querypart, 0, 7) == 'added:[') { $q = substr($querypart, 7, -1); if($t = explode(' to ', $q, 2)) { $astart = strtotime($t[0]); $aend = strtotime($t[1])+86400; // echo "astart: ".date('Y-m-d', $astart)."\n"; // echo "aend: ".date('Y-m-d', $aend); } } /* 'created' is the time when a document was actually created. * There is no equivalent in the fulltext index. * Currently is identical to 'added'. */ elseif(substr($querypart, 0, 9) == 'created:[') { $q = substr($querypart, 9, -1); if($t = explode(' to ', $q, 2)) { $astart = strtotime($t[0]); $aend = strtotime($t[1])+86400; // echo "astart: ".date('Y-m-d', $astart)."\n"; // echo "aend: ".date('Y-m-d', $aend); } } /* 'modified' is the time when a document was last modified. * This is 'modified' in the fulltext index. */ elseif(substr($querypart, 0, 10) == 'modified:[') { $q = substr($querypart, 10, -1); if($t = explode(' to ', $q, 2)) { $mstart = strtotime($t[0]); $mend = strtotime($t[1])+86400; // echo "mstart: ".date('Y-m-d', $mstart)."\n"; // echo "mend: ".date('Y-m-d', $mend); } } else { $query = $querypart; } } } elseif (isset($params["title_content"]) && is_string($params["title_content"])) { $query = $params['title_content']; } elseif (isset($params["title__icontains"]) && is_string($params["title__icontains"])) { $query = $params['title__icontains']; } $limit = isset($params['page_size']) ? (int) $params['page_size'] : 25; $page = (isset($params['page']) && $params['page'] > 0) ? (int) $params['page'] : 1; $offset = ($page-1)*$limit; /* Truncate content if requested * See https://github.com/paperless-ngx/paperless-ngx/blob/main/src/documents/serialisers.py */ $truncate_content = isset($params['truncate_content']) && ($params['truncate_content'] == 'true'); $order = []; if (isset($params["ordering"]) && is_string($params["ordering"])) { if($params["ordering"][0] == '-') { $order['dir'] = 'desc'; $orderfield = substr($params["ordering"], 1); } else { $order['dir'] = 'asc'; $orderfield = $params["ordering"]; } 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(); } } } /* Searching for tags (category) {{{ */ $categories = array(); $categorynames = array(); if(isset($params['tags__id'])) { $catid = (int) $params['tags__id']; if($catid) { if($cat = $dms->getDocumentCategory($catid)) { $categories[] = $cat; $categorynames[] = $cat->getName(); } } } /* tags__id__in is used when searching for documents by id */ if(isset($params['tags__id__all'])) { $catids = explode(',', $params['tags__id__all']); foreach($catids as $catid) { if($catid) { if($cat = $dms->getDocumentCategory($catid)) { $categories[] = $cat; $categorynames[] = $cat->getName(); } } } } /* tags__id__in is used when getting the documents of the inbox */ if(isset($params['tags__id__in'])) { $catids = explode(',', $params['tags__id__in']); if($catids) { foreach($catids as $catid) { if($cat = $dms->getDocumentCategory($catid)) { $categories[] = $cat; $categorynames[] = $cat->getName(); } } } } elseif(isset($params['is_tagged']) && $params['is_tagged'] == '1') { $categorynames[] = '*'; } /* }}} */ /* more_like_id is set to find similar documents {{{ */ if(isset($params['more_like_id'])) { if($fulltextservice && $index = $fulltextservice->Indexer()) { $lucenesearch = $fulltextservice->Search(); if($searchhit = $lucenesearch->getDocument((int) $params['more_like_id'])) { $idoc = $searchhit->getDocument(); if($idoc) { try { $fullcontent = $idoc->getFieldValue('content'); } catch (Exception $e) { $fullcontent = ''; } $wcl = 2000; $shortcontent = mb_strimwidth($fullcontent, 0, $wcl); /* Create a list of words and its occurences to be passed * to the classification. * The '.' is added as valid character in a word, because solr's * standard tokenizer treats it as a valid char as well. * But sqlitefts treats '.' as a separator */ $wordcount = self::mb_word_count($shortcontent, MB_CASE_LOWER, ''); arsort($wordcount); $newquery = []; foreach($wordcount as $word=>$n) { if(mb_strlen($word) > 4 && ($n > 2 || count($newquery) < 5)) $newquery[] = $word; } // echo implode(' ', $newquery); $logger->log("Query for '".implode(' ', $newquery)."'", PEAR_LOG_DEBUG); /* $newquery is empty if the document doesn't have a fulltext. * In that case it makes no sense to search for similar documents * Otherwise search for documents with newquery, but if doesn't yield * a result, short the newquery by the last term and try again until * newquery is void */ while($newquery) { $searchresult = $lucenesearch->search(implode(' ', $newquery), array('record_type'=>['document'], 'status'=>[2], 'user'=>[$userobj->getLogin()], 'startFolder'=>$startfolder, 'rootFolder'=>$rootfolder), array('limit'=>$limit, 'offset'=>$offset), $order); if($searchresult) { $recs = array(); if($searchresult['hits']) { $allids = ''; foreach($searchresult['hits'] as $hit) { if(($hit['document_id'][0] == 'D') && ($hit['document_id'] != 'D'.((int)$params['more_like_id']))) { if($tmp = $dms->getDocument((int) substr($hit['document_id'], 1))) { $allids .= $hit['document_id'].' '; $recs[] = $this->__getDocumentData($tmp, $truncate_content); } } else { $searchresult['count']--; } } $logger->log('Result is '.$allids, PEAR_LOG_DEBUG); if($recs) return $this->renderer->json($response, array('count'=>$searchresult['count'], 'next'=>null, 'previous'=>null, 'offset'=>$offset, 'limit'=>$limit, 'results'=>$recs))->withStatus(200); else { /* Still nothing found, so try a shorter query */ array_pop($newquery); } } else { /* Still nothing found, so try a shorter query */ array_pop($newquery); } } else { /* Quit the while loop right away, if the search failed */ $newquery = false; } } } } } return $this->renderer->json($response, array('count'=>0, 'next'=>null, 'previous'=>null, 'offset'=>0, 'limit'=>$limit, 'results'=>[]))->withStatus(200); /* Get all documents in the same folder and subfolders $likeid = (int) $params['more_like_id']; if($likeid && $likedoc = $dms->getDocument($likeid)) { $startfolder = $likedoc->getFolder(); } */ } /* }}} */ /* Search for correspondent {{{ */ $cattrs = []; $correspondent = null; if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { if(isset($params['correspondent__id']) && $params['correspondent__id']>0) { $valueset = $attrdef->getValueSetAsArray(); if(isset($valueset[$params['correspondent__id']-1])) { $correspondent = $valueset[$params['correspondent__id']-1]; $cattrs['attr_'.$attrdef->getId()] = $correspondent; } } if(isset($params['correspondent__id__in']) && $params['correspondent__id__in']>0) { $valueset = $attrdef->getValueSetAsArray(); $kkk = explode(',', $params['correspondent__id__in']); foreach($kkk as $kk) { if(isset($valueset[$kk-1])) { $correspondent = $valueset[$kk-1]; $cattrs['attr_'.$attrdef->getId()][] = $correspondent; } } } /* Search for any correspondent (correspondent__isnull = 0) */ if(isset($params['correspondent__isnull']) && $params['correspondent__isnull'] == '0') { $cattrs['attr_'.$attrdef->getId()] = '__any__'; } /* Search for no correspondent (correspondent__isnull = 1) */ if(isset($params['correspondent__isnull']) && $params['correspondent__isnull'] == '1') { $cattrs['attr_'.$attrdef->getId()] = '__notset__'; } } /* }}} */ /* Search for document type {{{ */ $documenttype = null; if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { if(isset($params['document_type__id']) && $params['document_type__id']>0) { $valueset = $attrdef->getValueSetAsArray(); if(isset($valueset[$params['document_type__id']-1])) { $documenttype = $valueset[$params['document_type__id']-1]; $cattrs['attr_'.$attrdef->getId()] = $documenttype; } } if(isset($params['document_type__id__in']) && $params['document_type__id__in']>0) { $valueset = $attrdef->getValueSetAsArray(); $kkk = explode(',', $params['document_type__id__in']); foreach($kkk as $kk) { if(isset($valueset[$kk-1])) { $documenttype = $valueset[$kk-1]; $cattrs['attr_'.$attrdef->getId()][] = $documenttype; } } } } /* Search for any document_type (document_type__isnull = 0) */ /* Search for no document_type (document_type__isnull = 1) */ /* }}} */ /* Search for storage path {{{ */ if(isset($params['storage_path__id']) && $params['storage_path__id']>0) { if($f = $dms->getFolder((int) $params['storage_path__id'])) $startfolder = $f; } /* storage_path__id__in is used serveral path */ if(isset($params['storage_path__id__in'])) { $spathids = explode(',', $params['storage_path__id__in']); if($spathids) { foreach($spathids as $spathid) { if($spath = $dms->getfolder((int) $spathid)) { // FIXME: $startfolder should be an array, but this is not supported // by the full text search. Could be changed $startfolder = $spath; // $categories[] = $cat; // $categorynames[] = $cat->getName(); } } } } /* }}} */ /* The start and end date for e.g. 2012-12-10 is * 2022-12-09 and 2022-12-11 * Because makeTsFromDate() returns the start of the day * one day has to be added. */ if(isset($params['added__date__gt'])) { $astart = (int) makeTsFromDate($params['added__date__gt'])+86400; } if(isset($params['added__date__lt'])) { $aend = (int) makeTsFromDate($params['added__date__lt']); } if(isset($params['created__date__gt'])) { $astart = (int) makeTsFromDate($params['created__date__gt'])+86400; } if(isset($params['created__date__lt'])) { $aend = (int) makeTsFromDate($params['created__date__lt']); } if($fulltextservice && $index = $fulltextservice->Indexer()) { $lucenesearch = $fulltextservice->Search(); $logger->log('Query is '.$query, PEAR_LOG_DEBUG); $filter = array('record_type'=>['document'], 'status'=>[2], 'user'=>[$userobj->getLogin()], 'category'=>$categorynames, 'created_start'=>$astart, 'created_end'=>$aend, 'modified_start'=>$mstart, 'modified_end'=>$mend, 'startFolder'=>$startfolder, 'rootFolder'=>$rootfolder, 'attributes'=>$cattrs); $logger->log('Attributes: '.var_export($cattrs, true), PEAR_LOG_DEBUG); $searchresult = $lucenesearch->search($query, $filter, 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 = ''; foreach($searchresult['hits'] as $hit) { if($hit['document_id'][0] == 'D') { if($tmp = $dms->getDocument((int) substr($hit['document_id'], 1))) { $allids .= $hit['document_id'].' '; // if($tmp->getAccessMode($user) >= M_READ) { // $tmp->verifyLastestContentExpriry(); $recs[] = $this->__getDocumentData($tmp, $truncate_content); // } } } } $logger->log('Result is '.$allids, PEAR_LOG_DEBUG); } if($offset + $limit < $searchresult['count']) { $params['page'] = $page+1; $next = $request->getUri()->getPath().'?'.http_build_query($params); } else $next = null; if($offset > 0) { $params['page'] = $page-1; $prev = $request->getUri()->getPath().'?'.http_build_query($params); } else $prev = null; return $this->renderer->json($response, array('count'=>$searchresult['count'], 'next'=>$next, 'previous'=>$prev, 'offset'=>$offset, 'limit'=>$limit, 'results'=>$recs))->withStatus(200); } } } return $response->withStatus(500); } /* }}} */ function document($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $document = $dms->getDocument($args['id']); if($document) { if($document->getAccessMode($userobj) >= M_READ) { /* get untruncated content from file */ $rec = $this->__getDocumentData($document, true, true); return $this->renderer->json($response, $rec)->withStatus(200); } else { return $response->withStatus(404); } } return $response->withStatus(500); } /* }}} */ /** * autocompletion is done on the last term of a list of comma separated * terms. The returned value is then a list of the first n-1 terms * concatenated with the completed terms, e.g. * 'term1 ter' will be auto completed to 'term1 term2', 'term1 term3', * etc. */ function autocomplete($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if(!empty($settings->_extensions['paperless']['autocompletefield'])) $field = $settings->_extensions['paperless']['autocompletefield']; else $field = 'title'; $params = $request->getQueryParams(); $allterms = explode(' ', $params['term']); $query = trim(array_pop($allterms)); $logger->log(var_export($params, true), PEAR_LOG_DEBUG); $list = []; if($fulltextservice && ($index = $fulltextservice->Indexer())) { if($terms = $index->terms($query, $field)) { foreach($terms as $term) $list[] = implode(' ', $allterms).' '.$term->text; } } return $this->renderer->json($response, $list)->withStatus(200); } /* }}} */ function search($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); $params = $request->getQueryParams(); // $params['query']; // $params['db_only']; $list = []; $list["total"] = 0; $list["documents"] = []; $list["saved_views"] = []; $list["tags"] = []; $list["correspondents"] = []; $list["document_types"] = []; $list["storage_paths"] = []; $list["users"] = []; $list["groups"] = []; $list["mail_rules"] = []; $list["mail_accounts"] = []; $list["workflows"] = []; $list["custom_fields"] = []; $total = 0; /* Search for saved views */ /* Search for saved tags */ if($categories = $dms->getDocumentCategories()) { $inboxtags = []; if(!empty($settings->_extensions['paperless']['inboxtags'])) $inboxtags = explode(',', $settings->_extensions['paperless']['inboxtags']); foreach ($categories as $category) { if (false !== stristr($category->getName(), $params['query'])) { $list["tags"][] = $this->__getCategoryData($category, $inboxtags); $total++; } } } /* Search for users */ if($allusers = $dms->getAllUsers()) { foreach ($allusers as $u) { if (false !== stristr($u->getFullName().' '.$u->getLogin(), $params['query'])) { $list["users"][] = $this->__getUserData($u); $total++; } } } $list["total"] = $total; return $this->renderer->json($response, $list)->withStatus(200); } /* }}} */ function ui_settings($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $data = [ 'user'=> [ 'id'=>$userobj->getId(), 'username'=>$userobj->getLogin(), 'is_staff'=>$userobj->isAdmin() || $userobj->isUser(), 'is_superuser'=>$userobj->isAdmin(), 'groups'=>[] ], 'settings'=> [ 'update_checking'=>array( 'enabled'=>false, 'backend_setting'=>'default' ), 'bulk_edit'=>array( 'apply_on_close'=>false, 'confirmation_dialogs'=>true ), 'documentListSize'=>50, 'slim_sidebar'=>false, 'dark_mode'=>array( 'use_system'=>true, 'enabled'=>false, // paperless-ngx 1.13.0 returns a string 'thumb_inverted'=>true, // paperless-ngx 1.13.0 returns a string ), 'theme'=>array( 'color'=>'', ), 'document_details'=>array( 'native_pdf_viewer'=>false, ), 'date_display'=>array( 'date_local'=>'', 'date_format'=>'mediumDate', ), 'notifications'=>array( 'consumer_new_documents'=>true, 'consumer_success'=>true, 'consumer_failed'=>true, 'consumer_suppress_on_dashboard'=>true, ), 'comments_enabled'=>true, 'language'=>'en-gb', 'trash_delay'=>30, 'app_title'=>null, 'app_logo'=>null, 'auditlog_enabled'=>true, 'email_enabled'=>false ], 'permissions'=>[ 'change_customfield', 'add_emailaddress', 'delete_chordcounter', 'view_schedule', 'change_contenttype', 'delete_logentry', 'delete_group', 'view_chordcounter', 'add_tag', 'add_savedviewfilterrule', 'view_groupobjectpermission', 'view_socialtoken', 'add_socialtoken', 'change_uisettings', 'change_groupobjectpermission', 'change_document', 'add_note', 'add_authenticator', 'delete_socialtoken', 'change_logentry', 'add_tokenproxy', 'view_failure', 'add_documenttype', 'view_tokenproxy', 'change_authenticator', 'delete_task', 'change_task', 'delete_savedview', 'view_document', 'delete_userobjectpermission', 'delete_schedule', 'change_mailrule', 'add_chordcounter', 'delete_workflowactionwebhook', 'delete_mailaccount', 'view_success', 'change_note', 'add_workflowtrigger', 'view_group', 'add_customfield', 'view_savedview', 'delete_correspondent', 'delete_token', 'view_tag', 'delete_note', 'change_savedviewfilterrule', 'view_applicationconfiguration', 'delete_socialaccount', 'delete_mailrule', 'add_user', 'view_workflowtrigger', 'delete_processedmail', 'add_sharelink', 'delete_workflowrun', 'delete_groupobjectpermission', 'add_uisettings', 'add_workflowaction', 'delete_log', 'delete_permission', 'change_permission', 'delete_emailaddress', 'view_workflowaction', 'change_tokenproxy', 'change_correspondent', 'add_failure', 'change_workflowrun', 'delete_document', 'add_correspondent', 'delete_savedviewfilterrule', 'add_processedmail', 'add_socialapp', 'view_logentry', 'view_socialaccount', 'view_workflowactionwebhook', 'add_socialaccount', 'view_documenttype', 'change_log', 'delete_paperlesstask', 'add_permission', 'delete_workflowactionemail', 'change_success', 'change_chordcounter', 'change_workflowactionwebhook', 'change_failure', 'add_logentry', 'delete_ormq', 'change_socialtoken', 'change_userobjectpermission', 'change_applicationconfiguration', 'view_processedmail', 'change_emailaddress', 'view_comment', 'change_savedview', 'view_note', 'add_document', 'delete_customfieldinstance', 'delete_applicationconfiguration', 'add_ormq', 'add_logentry', 'view_task', 'view_authenticator', 'view_savedviewfilterrule', 'add_schedule', 'add_savedview', 'change_socialapp', 'add_groupresult', 'change_storagepath', 'add_userobjectpermission', 'delete_socialapp', 'change_workflow', 'view_emailconfirmation', 'view_mailrule', 'view_token', 'change_socialaccount', 'view_ormq', 'add_mailaccount', 'delete_storagepath', 'view_correspondent', 'change_mailaccount', 'add_group', 'change_tag', 'add_success', 'view_permission', 'delete_comment', 'delete_workflowtrigger', 'view_session', 'delete_logentry', 'add_emailconfirmation', 'add_taskresult', 'delete_failure', 'add_token', 'view_contenttype', 'add_workflowactionemail', 'view_customfield', 'change_group', 'add_log', 'delete_uisettings', 'delete_customfield', 'change_processedmail', 'change_user', 'add_workflow', 'change_emailconfirmation', 'delete_authenticator', 'delete_sharelink', 'view_paperlesstask', 'delete_taskresult', 'view_user', 'add_groupobjectpermission', 'view_storagepath', 'view_groupresult', 'view_customfieldinstance', 'change_ormq', 'change_workflowactionemail', 'view_sharelink', 'change_workflowtrigger', 'change_documenttype', 'add_comment', 'view_taskresult', 'delete_groupresult', 'add_storagepath', 'change_logentry', 'view_socialapp', 'add_workflowactionwebhook', 'view_workflowactionemail', 'change_session', 'change_paperlesstask', 'view_log', 'view_userobjectpermission', 'delete_session', 'view_uisettings', 'change_comment', 'delete_contenttype', 'delete_workflow', 'add_session', 'delete_user', 'delete_workflowaction', 'add_workflowrun', 'delete_success', 'view_logentry', 'change_taskresult', 'add_customfieldinstance', 'add_applicationconfiguration', 'change_schedule', 'add_contenttype', 'view_mailaccount', 'change_token', 'change_groupresult', 'add_mailrule', 'add_paperlesstask', 'view_workflowrun', 'add_task', 'view_workflow', 'delete_emailconfirmation', 'change_sharelink', 'change_customfieldinstance', 'view_emailaddress', 'delete_tag', 'delete_tokenproxy', 'change_workflowaction', 'delete_documenttype' ] ]; /* $data = array( 'user'=>[ 'id'=>$userobj->getId(), 'username'=>$userobj->getLogin(), 'is_superuser'=>$userobj->isAdmin(), 'groups'=>[] ], 'settings'=>array('update_checking'=>array('backend_setting'=>'default')), ); */ return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ function statstotal($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if(false === ($categories = $dms->getDocumentCategories())) { return $this->renderer->json($response, array('results'=>null))->withStatus(500); } if(!empty($settings->_extensions['paperless']['usehomefolder'])) { if(!($startfolder = $dms->getFolder((int) $userobj->getHomeFolder()))) $startfolder = $dms->getFolder($settings->_rootFolderID); } elseif(!isset($settings->_extensions['paperless']['rootfolder']) || !($startfolder = $dms->getFolder($settings->_extensions['paperless']['rootfolder']))) $startfolder = $dms->getFolder($settings->_rootFolderID); $data = array( 'documents_total'=>0, 'documents_inbox'=>0, ); if($fulltextservice && ($index = $fulltextservice->Indexer())) { $lucenesearch = $fulltextservice->Search(); $searchresult = $lucenesearch->search('', array('record_type'=>['document'], 'status'=>[2], 'user'=>[$userobj->getLogin()], 'startFolder'=>$startfolder, 'rootFolder'=>$startfolder), array('limit'=>1), array(), array('no_facets'=>false)); if($searchresult === false) { return $response->withStatus(500); } else { $recs = array(); $facets = $searchresult['facets']; $logger->log('Facets: '.var_export($facets, true), PEAR_LOG_DEBUG); if ($facets['mimetype']) { $data['document_file_type_counts'] = []; foreach ($facets['mimetype'] as $mt=>$c) { $data['document_file_type_counts'][] = ['mime_type'=>$mt, 'mime_type_count'=>$c]; } } } $data['documents_total'] = $searchresult['count']; $inboxtags = []; if(!empty($settings->_extensions['paperless']['inboxtags']) && $inboxtags = explode(',', $settings->_extensions['paperless']['inboxtags'])) { $data['inbox_tags'] = $inboxtags; foreach($inboxtags as $inboxtagid) { $cats = []; if($inboxtag = $dms->getDocumentCategory((int) $inboxtagid)) { $cats[] = $inboxtag->getName(); } if($cats) { $searchresult = $lucenesearch->search('', array('record_type'=>['document'], 'status'=>[2], 'user'=>[$userobj->getLogin()], 'category'=>$cats, 'startFolder'=>$startfolder, 'rootFolder'=>$startfolder), array('limit'=>1), array(), array('no_facets'=>true)); if($searchresult === false) { return $response->withStatus(500); } $data['documents_inbox'] = $searchresult['count']; } } } $cats = $dms->getDocumentCategories(); $data['tag_count'] = count($cats); if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { $valueset = $attrdef->getValueSetAsArray(); $data['correspondent_count'] = count($valueset); } if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { $valueset = $attrdef->getValueSetAsArray(); $data['document_type_count'] = count($valueset); } } return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ function status($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); $v = new \Seeddms\Seeddms\Version(); $data = [ 'pngx_version' => $v->version(), 'server_os' => php_uname(), 'install_type' => '', 'storage' => [ 'total' => 0, 'available' => 0 ], 'database' => [ 'type' => $settings->_dbDriver, 'url' => $settings->_dbHostname, 'status' => 'OK', 'error' => null ], ]; return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ function trash($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $fulltextservice = $this->container->get('fulltextservice'); $logger = $this->container->get('logger'); if ($settings->extensionIsDisabled('trash') || empty($settings->_extensions['trash']['trashid'])) { $v = new \Seeddms\Seeddms\Version(); $data = [ 'count'=>0, 'next'=>null, 'previous'=>null, 'all'=>[], 'results'=>[] ]; } else { $trashfolder = $dms->getFolder($settings->_extensions['trash']['trashid']); if ($trashfolder) { $documents = $trashfolder->getDocuments(); $documents = SeedDMS_Core_DMS::filterAccess($documents, $userobj, M_READ); $recs = []; $alls = []; foreach ($documents as $document) { $recs[] = $this->__getDocumentData($document, false, false); $alls[] = $document->getId(); } if($recs) return $this->renderer->json($response, array('count'=>count($recs), 'next'=>null, 'previous'=>null, 'all'=>$alls, 'results'=>$recs))->withStatus(200); } else { $data = [ 'count'=>0, 'next'=>null, 'previous'=>null, 'all'=>[], 'results'=>[] ]; } } return $this->renderer->json($response, $data)->withStatus(200); } /* }}} */ function fetch_thumb($request, $response, $args) { /* {{{ */ $p = strpos($request->getUri()->getPath(), '/fetch/thumb'); return $response->withHeader('Location', substr($request->getUri()->getPath(), 0, $p).'/api/documents/'.$args['id'].'/thumb/')->withStatus(302); } /* }}} */ function documents_thumb($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $document = $dms->getDocument($args['id']); if($document) { if($document->getAccessMode($userobj) >= M_READ) { $object = $document->getLatestContent(); $width = 400; $previewer = new SeedDMS_Preview_Previewer($settings->_cacheDir, $width); if($conversionmgr) $previewer->setConversionMgr($conversionmgr); else $previewer->setConverters($settings->_converters['preview']); if(!$previewer->hasPreview($object)) $previewer->createPreview($object); $file = $previewer->getFileName($object, $width).".png"; if(!file_exists($file)) { $file = __DIR__.'/res/preview-error.png'; $response->getBody()->write(file_get_contents($file)); return $response->withHeader('Content-Type', 'image/png') ->withHeader('Content-Description', 'File Transfer') ->withHeader('Content-Transfer-Encoding', 'binary') ->withHeader('Content-Disposition', 'attachment; filename="preview-error.png') ->withHeader('Content-Length', filesize($file)); } else { $response->getBody()->write(file_get_contents($file)); return $response->withHeader('Content-Type', 'image/png') ->withHeader('Content-Description', 'File Transfer') ->withHeader('Content-Transfer-Encoding', 'binary') ->withHeader('Content-Disposition', 'attachment; filename="preview-' . $document->getID() . "-" . $object->getVersion() . "-" . $width . ".png" . '"') ->withHeader('Content-Length', $previewer->getFilesize($object)); } } } return $response->withStatus(403); } /* }}} */ function fetch_doc($request, $response, $args) { /* {{{ */ $logger = $this->container->get('logger'); $logger->log('Fetch doc '.$args['id'], PEAR_LOG_INFO); $p = strpos($request->getUri()->getPath(), '/fetch/doc'); return $response->withHeader('Location', substr($request->getUri()->getPath(), 0, $p).'/api/documents/'.$args['id'].'/download/')->withStatus(302); } /* }}} */ /** * documents_preview works like documents_download but converts * documents which are not pdf already into pdf */ function documents_preview($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $logger->log('Get preview of doc '.$args['id'], PEAR_LOG_INFO); $document = $dms->getDocument($args['id']); if($document) { if($document->getAccessMode($userobj) >= M_READ) { $lc = $document->getLatestContent(); if($lc) { if($lc->getMimeType() == 'application/pdf') { if (pathinfo($document->getName(), PATHINFO_EXTENSION) == $lc->getFileType()) $filename = $document->getName(); else $filename = $document->getName().$lc->getFileType(); $file = $dms->contentDir . $lc->getPath(); if(!file_exists($file)) { return $this->renderer->json($response, array('success'=>false, 'message'=>'', 'data'=>''))->withStatus(500); } $filesize = filesize($dms->contentDir . $lc->getPath()); } else { $previewer = new SeedDMS_Preview_PdfPreviewer($settings->_cacheDir); if($conversionmgr) $previewer->setConversionMgr($conversionmgr); else $previewer->setConverters(isset($settings->_converters['pdf']) ? $settings->_converters['pdf'] : array()); if(!$previewer->hasPreview($lc)) $previewer->createPreview($lc); if(!$previewer->hasPreview($lc)) { $logger->log('Creating pdf preview failed', PEAR_LOG_ERR); return $this->renderer->json($response, array('success'=>false, 'message'=>'Creating pdf preview failed', 'data'=>''))->withStatus(500); } else { $filename = $document->getName().".pdf"; $file = $previewer->getFileName($lc).".pdf"; if(!file_exists($file)) { $logger->log('Creating pdf preview failed', PEAR_LOG_ERR); return $this->renderer->json($response, array('success'=>false, 'message'=>'Creating pdf preview failed', 'data'=>''))->withStatus(500); } $filesize = filesize($file); } } $response->getBody()->write(file_get_contents($file)); return $response->withHeader('Content-Type', $lc->getMimeType()) ->withHeader('Content-Description', 'File Transfer') ->withHeader('Content-Transfer-Encoding', 'binary') ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') ->withHeader('Content-Length', $filesize) ->withHeader('Expires', '0') ->withHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') ->withHeader('Pragma', 'no-cache'); } else { return $response->withStatus(403); } } else return $response->withStatus(404); } else { return $response->withStatus(500); } } /* }}} */ function documents_download($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $params = $request->getQueryParams(); $logger->log(var_export($params, true), PEAR_LOG_DEBUG); $logger->log('Download doc '.$args['id'], PEAR_LOG_INFO); $document = $dms->getDocument($args['id']); if($document) { if($document->getAccessMode($userobj) >= M_READ) { $lc = $document->getLatestContent(); if($lc) { /* Used to check if empty($settings->_extensions['paperless']['converttopdf']) * but that makes no sense any more, because paperless mobile sets * the parameter 'original=true' if the original document shall be * downloaded. */ if((isset($params['original']) && $params['original'] == 'true') || $lc->getMimeType() == 'application/pdf') { if (pathinfo($document->getName(), PATHINFO_EXTENSION) == $lc->getFileType()) $filename = $document->getName(); else $filename = $document->getName().$lc->getFileType(); $file = $dms->contentDir . $lc->getPath(); if(!file_exists($file)) { return $this->renderer->json($response, array('success'=>false, 'message'=>'', 'data'=>''))->withStatus(500); } $filesize = filesize($dms->contentDir . $lc->getPath()); } else { $previewer = new SeedDMS_Preview_PdfPreviewer($settings->_cacheDir); if($conversionmgr) $previewer->setConversionMgr($conversionmgr); else $previewer->setConverters(isset($settings->_converters['pdf']) ? $settings->_converters['pdf'] : array()); if(!$previewer->hasPreview($lc)) $previewer->createPreview($lc); if(!$previewer->hasPreview($lc)) { $logger->log('Creating pdf preview failed', PEAR_LOG_ERR); return $this->renderer->json($response, array('success'=>false, 'message'=>'Creating pdf preview failed', 'data'=>''))->withStatus(500); } else { $filename = $document->getName().".pdf"; $file = $previewer->getFileName($lc).".pdf"; if(!file_exists($file)) { $logger->log('Creating pdf preview failed', PEAR_LOG_ERR); return $this->renderer->json($response, array('success'=>false, 'message'=>'Creating pdf preview failed', 'data'=>''))->withStatus(500); } $filesize = filesize($file); } } $response->getBody()->write(file_get_contents($file)); return $response->withHeader('Content-Type', $lc->getMimeType()) ->withHeader('Content-Description', 'File Transfer') ->withHeader('Content-Transfer-Encoding', 'binary') ->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') ->withHeader('Content-Length', $filesize) ->withHeader('Expires', '0') ->withHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') ->withHeader('Pragma', 'no-cache'); } else { return $response->withStatus(403); } } else return $response->withStatus(404); } else { return $response->withStatus(500); } } /* }}} */ function documents_metadata($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $document = $dms->getDocument($args['id']); if($document) { if($document->getAccessMode($userobj) >= M_READ) { $lc = $document->getLatestContent(); if($lc) { return $this->renderer->json($response, array( 'original_checksum'=>$lc->getChecksum(), 'original_size'=>(int) $lc->getFilesize(), 'original_mime_type'=>$lc->getMimeType(), 'media_filename'=>$lc->getOriginalFileName(), 'has_archive_version'=>false, 'original_metadata'=>[], 'archive_checksum'=>$lc->getChecksum(), 'archive_media_filename'=>$lc->getOriginalFileName(), 'original_filename'=>$lc->getOriginalFileName(), 'lang'=>'en', 'archive_size'=>(int) $lc->getFilesize(), 'archive_metadata'=>[], ))->withStatus(200); } else { return $response->withStatus(403); } } else return $response->withStatus(404); } else { return $response->withStatus(500); } } /* }}} */ function documents_notes($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $document = $dms->getDocument($args['id']); if($document) { if($document->getAccessMode($userobj) >= M_READ) { $owner = $document->getOwner(); $lc = $document->getLatestContent(); if($lc) { $notes = []; if ($document->getComment()) { $notes[] = [ 'id'=>$lc->getId(), 'note'=>$document->getComment(), 'created'=>date('Y-m-d\TH:i:s+02:00', $lc->getDate()), "user"=>[ 'id'=>$owner->getId(), 'username'=>$owner->getLogin(), 'first_name'=>"", 'last_name'=>$owner->getFullName() ] ]; } return $this->renderer->json($response, $notes)->withStatus(200); } else { return $response->withStatus(403); } } else { return $response->withStatus(403); } } else { return $response->withStatus(404); } } /* }}} */ function documents_history($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $document = $dms->getDocument($args['id']); if($document) { if($document->getAccessMode($userobj) >= M_READ) { return $this->renderer->json($response, [])->withStatus(200); } else { return $response->withStatus(403); } } else { return $response->withStatus(404); } } /* }}} */ function post_document($request, $response) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $fulltextservice = $this->container->get('fulltextservice'); $notifier = $this->container->get('notifier'); $data = $request->getParsedBody(); $mfolder = null; if(isset($settings->_extensions['paperless']['uploadfolder'])) $mfolder = $dms->getFolder($settings->_extensions['paperless']['uploadfolder']); if(!$mfolder) { if(!empty($settings->_extensions['paperless']['usehomefolder'])) { if(!($mfolder = $dms->getFolder((int) $userobj->getHomeFolder()))) $mfolder = $dms->getFolder($settings->_rootFolderID); } } /* We use the storage path to put the document into the right folder */ if(!$mfolder) { if (!empty($data['storage_path'])) $mfolder = $dms->getFolder((int) $data['storage_path']); } if(!$mfolder) { if(!isset($settings->_extensions['paperless']['rootfolder']) || !($mfolder = $dms->getFolder($settings->_extensions['paperless']['rootfolder']))) $mfolder = $dms->getFolder($settings->_rootFolderID); } if($mfolder) { $logger->log("Uploading into folder '".$mfolder->getName()."' (".$mfolder->getId().")", PEAR_LOG_DEBUG); if($mfolder->getAccessMode($userobj) < M_READWRITE) { $logger->log('No write access on folder '.$mfolder->getId(), PEAR_LOG_ERR); return $response->withStatus(403); } $logger->log(var_export($data, true), PEAR_LOG_DEBUG); $uploadedFiles = $request->getUploadedFiles(); if (count($uploadedFiles) == 0) { $logger->log('No files uploaded', PEAR_LOG_ERR); return $this->renderer->json($response, getMLText("paperless_no_files_uploaded"))->withStatus(400); } $file_info = array_pop($uploadedFiles); $maxuploadsize = SeedDMS_Core_File::parse_filesize($settings->_maxUploadSize); if ($maxuploadsize && $file_info->getSize() > $maxuploadsize) { $logger->log('File too large ('.$file_info->getSize().' > '.$maxuploadsize.')', PEAR_LOG_ERR); return $this->renderer->json($response, getMLText("paperless_upload_maxsize"))->withStatus(400); } $origfilename = null; if ($origfilename == null) $origfilename = $file_info->getClientFilename(); if(!empty($data['title'])) $docname = $data['title']; else $docname = $origfilename; /* Check if name already exists in the folder */ if(!$settings->_enableDuplicateDocNames) { if($mfolder->hasDocumentByName($docname)) { $logger->log('Duplicate document name '.$docname, PEAR_LOG_ERR); return $this->renderer->json($response, getMLText("document_duplicate_name"))->withStatus(409); } } /* If several tags are set, they will all be saved individually in * a parameter named 'tags'. This cannot be handled by php. It would * require to name it 'tags[]'. Hence, only the last tag will be taken into * account. */ $cats = []; if(!empty($data['tags'])) { if($cat = $dms->getDocumentCategory((int) $data['tags'])) $cats[] = $cat; } $userfiletmp = tempnam(sys_get_temp_dir(), 'paperless'); file_put_contents($userfiletmp, (string) $file_info->getStream()); $finfo = finfo_open(FILEINFO_MIME_TYPE); $userfiletype = finfo_file($finfo, $userfiletmp); $fileType = ".".pathinfo($origfilename, PATHINFO_EXTENSION); finfo_close($finfo); $reviewers = array(); $approvers = array(); $reviewers["i"] = array(); $reviewers["g"] = array(); $approvers["i"] = array(); $approvers["g"] = array(); $workflow = null; if($settings->_workflowMode == 'traditional' || $settings->_workflowMode == 'traditional_only_approval') { // add mandatory reviewers/approvers if($settings->_workflowMode == 'traditional') { $mreviewers = getMandatoryReviewers($mfolder, null, $userobj); if($mreviewers['i']) $reviewers['i'] = array_merge($reviewers['i'], $mreviewers['i']); if($mreviewers['g']) $reviewers['g'] = array_merge($reviewers['g'], $mreviewers['g']); } $mapprovers = getMandatoryApprovers($mfolder, null, $userobj); if($mapprovers['i']) $approvers['i'] = array_merge($approvers['i'], $mapprovers['i']); if($mapprovers['g']) $approvers['g'] = array_merge($approvers['g'], $mapprovers['g']); } elseif($settings->_workflowMode == 'advanced') { if($workflows = $userobj->getMandatoryWorkflows()) { $workflow = array_shift($workflows); } } $comment = ''; $expires = null; $owner = null; $keywords = ''; $sequence = 1; $reqversion = null; $version_comment = ''; $attributes = array(); $attributes_version = array(); $notusers = array(); $notgroups = array(); if (!empty($data['correspondent'])) { if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { $valueset = $attrdef->getValueSetAsArray(); if(isset($valueset[$data['correspondent']-1])) { $attributes[$attrdef->getId()] = $valueset[$data['correspondent']-1]; } } } if (!empty($data['document_type'])) { if(!empty($settings->_extensions['paperless']['documenttypesattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['documenttypesattr'])) { $valueset = $attrdef->getValueSetAsArray(); if(isset($valueset[$data['document_type']-1])) { $attributes[$attrdef->getId()] = $valueset[$data['document_type']-1]; } } } if (!empty($data['custom_fields'])) { $cf = json_decode($data['custom_fields'], true); // $logger->log('Custom-Fields '.var_export($cf, true), PEAR_LOG_DEBUG); foreach ($cf as $cfid=>$cfvalue) { $attributes[$cfid] = $cfvalue; } } $controller = Controller::factory('AddDocument', array('dms'=>$dms, 'user'=>$userobj)); $controller->setParam('documentsource', 'paperless'); $controller->setParam('folder', $mfolder); $controller->setParam('fulltextservice', $fulltextservice); $controller->setParam('name', $docname); $controller->setParam('comment', $comment); $controller->setParam('expires', $expires); $controller->setParam('keywords', $keywords); $controller->setParam('categories', $cats); $controller->setParam('owner', $userobj); $controller->setParam('userfiletmp', $userfiletmp); $controller->setParam('userfilename', $origfilename ? $origfilename : basename($userfiletmp)); $controller->setParam('filetype', $fileType); $controller->setParam('userfiletype', $userfiletype); $controller->setParam('sequence', $sequence); $controller->setParam('reviewers', $reviewers); $controller->setParam('approvers', $approvers); $controller->setParam('reqversion', $reqversion); $controller->setParam('versioncomment', $version_comment); $controller->setParam('attributes', $attributes); $controller->setParam('attributesversion', $attributes_version); $controller->setParam('workflow', $workflow); $controller->setParam('notificationgroups', $notgroups); $controller->setParam('notificationusers', $notusers); $controller->setParam('maxsizeforfulltext', $settings->_maxSizeForFullText); $controller->setParam('defaultaccessdocs', $settings->_defaultAccessDocs); if(!$document = $controller()) { $err = $controller->getErrorMsg(); if(is_string($err)) $errmsg = getMLText($err); elseif(is_array($err)) { $errmsg = getMLText($err[0], $err[1]); } else { $errmsg = $err; } $logger->log('Upload failed: '.$errmsg, PEAR_LOG_ERR); return $this->renderer->json($response, getMLText('paperless_upload_failed'))->withStatus(500); } else { $logger->log('Upload succeeded', PEAR_LOG_INFO); /* Turn off for now, because file_info is not an array if($controller->hasHook('cleanUpDocument')) { $controller->callHook('cleanUpDocument', $document, $file_info); } */ // Send notification to subscribers of folder. if($notifier) { $notifier->sendNewDocumentMail($document, $userobj); } if(file_exists($userfiletmp)) { unlink($userfiletmp); } /* Create a fake uuid */ $data = random_bytes(16); $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100 $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 $uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); $response->getBody()->write($uuid); return $response->withStatus(200); } } return $this->renderer->json($response, getMLText('paperless_missing_target_folder'))->withStatus(400); } /* }}} */ function patch_documents($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); $fulltextservice = $this->container->get('fulltextservice'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $document = $dms->getDocument($args['id']); if($document) { $body = $request->getBody(); if($data = json_decode($body, true)) { if(isset($data['tags'])) { $cats = []; foreach($data['tags'] as $tagid) { if($cat = $dms->getDocumentCategory($tagid)) { $cats[] = $cat; } } if(!$document->setCategories($cats)) return $response->withStatus(500); if($fulltextservice && ($index = $fulltextservice->Indexer())) { $idoc = $fulltextservice->IndexedDocument($document); // if(false !== $this->callHook('preIndexDocument', $document, $idoc)) { $lucenesearch = $fulltextservice->Search(); if($hit = $lucenesearch->getDocument((int) $document->getId())) { $index->delete($hit->id); } $index->addDocument($idoc); $index->commit(); // } } } } } return $response->withStatus(204); } /* }}} */ /** * Currently just sets tags but receives all kind of data, which * is still disregarded. */ function put_documents($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); $fulltextservice = $this->container->get('fulltextservice'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $document = $dms->getDocument($args['id']); if($document) { $body = $request->getBody(); if($data = json_decode($body, true)) { $updatefulltext = false; $logger->log(var_export($data, true), PEAR_LOG_DEBUG); if(isset($data['tags'])) { $cats = []; foreach($data['tags'] as $tagid) { if($cat = $dms->getDocumentCategory($tagid)) { $cats[] = $cat; } } if(!$document->setCategories($cats)) return $response->withStatus(500); $updatefulltext = true; } if(isset($data['correspondent'])) { if(!empty($settings->_extensions['paperless']['correspondentsattr']) && $attrdef = $dms->getAttributeDefinition($settings->_extensions['paperless']['correspondentsattr'])) { $valueset = $attrdef->getValueSetAsArray(); if(isset($valueset[$data['correspondent']-1])) { $attrvalue = $valueset[$data['correspondent']-1]; $logger->log('set attribute '.$attrdef->getName().' to '.$attrvalue, PEAR_LOG_DEBUG); if(!$document->setAttributeValue($attrdef, $attrvalue)) { } $updatefulltext = true; } } } if($updatefulltext) { if($fulltextservice && ($index = $fulltextservice->Indexer())) { $idoc = $fulltextservice->IndexedDocument($document); // if(false !== $this->callHook('preIndexDocument', $document, $idoc)) { $lucenesearch = $fulltextservice->Search(); if($hit = $lucenesearch->getDocument((int) $document->getId())) { $index->delete($hit->id); } $index->addDocument($idoc); $index->commit(); // } } } } } return $this->renderer->json($response, $this->__getDocumentData($document))->withStatus(200); } /* }}} */ function delete_documents($request, $response, $args) { /* {{{ */ $dms = $this->container->get('dms'); $userobj = $this->container->get('userobj'); $settings = $this->container->get('config'); $conversionmgr = $this->container->get('conversionmgr'); $logger = $this->container->get('logger'); $notifier = $this->container->get('notifier'); $fulltextservice = $this->container->get('fulltextservice'); if (!isset($args['id']) || !$args['id']) return $response->withStatus(404); $document = $dms->getDocument($args['id']); if($document) { $folder = $document->getFolder(); /* Remove all preview images. */ $previewer = new SeedDMS_Preview_Previewer($settings->_cacheDir); $previewer->deleteDocumentPreviews($document); /* Get the notify list before removing the document * Also inform the users/groups of the parent folder * Getting the list now will keep them in the document object * even after the document has been deleted. */ $dnl = $document->getNotifyList(); $fnl = $folder->getNotifyList(); $docname = $document->getName(); $controller = Controller::factory('RemoveDocument', array('dms'=>$dms, 'user'=>$userobj)); $controller->setParam('document', $document); $controller->setParam('fulltextservice', $fulltextservice); if(!$controller()) { $logger->log($controller->getErrorMsg(), PEAR_LOG_ERR); return $response->withStatus(500); } $logger->log('Document deleted', PEAR_LOG_INFO); if ($notifier){ /* $document still has the data from the just deleted document, * which is just enough to send the email. */ $notifier->sendDeleteDocumentMail($document, $userobj); } } return $response->withStatus(204); } /* }}} */ } /* }}} */ class SeedDMS_ExtPaperless_RestAPI_Auth { /* {{{ */ private $container; private $responsefactory; private $renderer; public function __construct(ContainerInterface $container, $responsefactory, SeedDMS_ExtPaperless_JsonRenderer $renderer) { $this->container = $container; $this->responsefactory = $responsefactory; $this->renderer = $renderer; } /** * Example middleware invokable class * * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request * @param \Psr\Http\Message\ResponseInterface $response PSR7 response * @param callable $next Next middleware * * @return \Psr\Http\Message\ResponseInterface */ public function __invoke($request, $handler) { /* {{{ */ // $this->container has the DI $dms = $this->container->get('dms'); $settings = $this->container->get('config'); $logger = $this->container->get('logger'); $logger->log("Invoke paperless AuthMiddleware for method ".$request->getMethod()." on '".$request->getUri()->getPath()."'", PEAR_LOG_INFO); /* Skip this middleware if the authentication was already successful */ $userobj = null; if($this->container->has('userobj')) $userobj = $this->container->get('userobj'); $environment = $request->getServerParams(); $path = $environment['PATH_INFO'] ?? ''; if(!$userobj) { if(!in_array($path, array('/api/token/', '/api/remote_version/', '/api/schema/', '/api/'))) { if(!empty($environment['HTTP_AUTHORIZATION'])) { $tmp = explode(' ', $environment['HTTP_AUTHORIZATION'], 2); switch($tmp[0]) { case 'Token': $logger->log("Token authentication with ".$tmp[0]."=".$tmp[1], PEAR_LOG_INFO); /* if jwtsecret is set, the token is expected to be a jwt */ if(!empty($settings->_extensions['paperless']['jwtsecret'])) { $token = new SeedDMS_JwtToken($settings->_extensions['paperless']['jwtsecret']); if(!$tokenstr = $token->jwtDecode($tmp[1])) { $logger->log("Could not decode jwt", PEAR_LOG_ERR); $response = $this->responsefactory->createResponse(); return $this->renderer->json($response, array('success'=>false, 'message'=>'Could not decode token', 'data'=>''))->withStatus(403); } $tmp = explode(':', json_decode($tokenstr, true)); if($tmp[1] < time()) { $logger->log("Jwt has expired at ".date('Y-m-d H:i:s', $tmp[1]), PEAR_LOG_ERR); $response = $this->responsefactory->createResponse(); return $this->renderer->json($response, getMLText('paperless_token_has_expired'))->withStatus(403); } else { $logger->log("Token is valid till ".date('Y-m-d H:i:s', $tmp[1]), PEAR_LOG_DEBUG); } if(!($userobj = $dms->getUser((int) $tmp[0]))) { $logger->log("No such user ".$tmp[0], PEAR_LOG_ERR); $response = $this->responsefactory->createResponse(); return $this->renderer->json($response, array('success'=>false, 'message'=>'No such user', 'data'=>''))->withStatus(403); } $dms->setUser($userobj); if($this->container instanceof \Slim\Container) $this->container['userobj'] = $userobj; else $this->container->set('userobj', $userobj); $logger->log("Login with jwt as '".$userobj->getLogin()."' successful", PEAR_LOG_INFO); } else { if(!empty($settings->_apiKey) && !empty($settings->_apiUserId)) { if($settings->_apiKey == $tmp[1]) { if(!($userobj = $dms->getUser($settings->_apiUserId))) { $response = $this->responsefactory->createResponse(); return $response->withStatus(403); } } else { $logger->log("Login with apikey '".$tmp[1]."' failed", PEAR_LOG_ERR); $response = $this->responsefactory->createResponse(); return $response->withStatus(403); } $dms->setUser($userobj); if($this->container instanceof \Slim\Container) $this->container['userobj'] = $userobj; else $this->container->set('userobj', $userobj); $logger->log("Login with apikey as '".$userobj->getLogin()."' successful", PEAR_LOG_INFO); } } break; case 'Basic': $logger->log("Basic authentication with ".$tmp[0]."=".$tmp[1], PEAR_LOG_INFO); $authenticator = $this->container->get('authenticator'); $kk = explode(':', base64_decode($tmp[1])); $userobj = $authenticator->authenticate($kk[0], $kk[1]); if(!$userobj) { $logger->log("Login with basic authentication for '".$kk[0]."' failed", PEAR_LOG_ERR); $response = $this->responsefactory->createResponse(); return $response->withStatus(403); } $dms->setUser($userobj); if($this->container instanceof \Slim\Container) $this->container['userobj'] = $userobj; else $this->container->set('userobj', $userobj); $logger->log("Login with basic authentication as '".$userobj->getLogin()."' successful", PEAR_LOG_INFO); break; } } } else { /* Set userobj to keep other middlewares for authentication from running */ if($this->container instanceof \Slim\Container) $this->container['userobj'] = true; else $this->container->set('userobj', true); } } if(!$userobj) $logger->log("Not yet authenticated. Pass on to next middleware", PEAR_LOG_INFO); else $logger->log("Authenticated as ".(is_object($userobj) ? $userobj->getLogin() : "annon").". Pass on to next middleware", PEAR_LOG_INFO); /* Always pass on to the next middleware. If that middleware does * authentication, then it should first check if 'userobj' in the container * is already set. The authentication shipped with seeddms restapi does that * and skips its own authentication, if userobj already exists. */ $response = $handler->handle($request); /* Pretent to be paperless ngx 1.13.0 with api version 2 */ $response = $response->withHeader('x-api-version', !empty($settings->_extensions['paperless']['apiversion']) ? $settings->_extensions['paperless']['apiversion'] : '9')->withHeader('x-version', !empty($settings->_extensions['paperless']['version']) ? $settings->_extensions['paperless']['version'] : '2.20.9'); $logger->log("End of paperless AuthMiddleware for method ".$request->getMethod()." on '".$request->getUri()->getPath()."'", PEAR_LOG_INFO); return $response; } /* }}} */ } /* }}} */ /** * Class containing methods which adds additional routes to the RestAPI * * @author Uwe Steinmann * @package SeedDMS * @subpackage paperless */ class SeedDMS_ExtPaperless_RestAPI { /* {{{ */ /** * Hook for adding additional routes to the RestAPI * * @param object $app instance of \Slim\App * @return void */ public function addMiddleware($app) { /* {{{ */ $container = $app->getContainer(); $app->add(new SeedDMS_ExtPaperless_RestAPI_Auth($container, $app->getResponseFactory(), new SeedDMS_ExtPaperless_JsonRenderer())); } /* }}} */ /** * Hook for adding additional routes to the RestAPI * * @param object $app instance of \Slim\App * @return void */ public function addRoute($app) { /* {{{ */ $app->get('/api/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':api'); /* /api/token/ is actually a get, but paperless_app calls it to check for ngx */ $app->post('/api/token/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':token'); $app->get('/api/token/', function($request, $response) use ($app) { return $response->withStatus(405); }); $app->get('/api/remote_version/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':remote_version'); $app->map(['HEAD'], '/api/schema/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':schema'); $app->get('/api/profile/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':profile'); $app->patch('/api/profile/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_profile'); $app->get('/api/tags/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':tags'); $app->get('/api/tags/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':tag'); $app->post('/api/tags/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':post_tag'); $app->patch('/api/tags/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_tag'); $app->delete('/api/tags/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':delete_tag'); // $app->put('/api/tags/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':update_tag'); $app->get('/api/users/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':users'); $app->get('/api/users/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':user'); $app->get('/api/documents/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':documents'); $app->get('/api/correspondents/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':correspondents'); $app->get('/api/correspondents/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':correspondent'); $app->post('/api/correspondents/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':post_correspondents'); $app->patch('/api/correspondents/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_correspondent'); $app->put('/api/correspondents/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_correspondent'); $app->delete('/api/correspondents/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':delete_correspondent'); $app->get('/api/document_types/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':document_types'); $app->get('/api/document_types/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':document_type'); $app->post('/api/document_types/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':post_document_types'); $app->patch('/api/document_types/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_document_type'); $app->put('/api/document_types/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_document_type'); $app->delete('/api/document_types/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':delete_document_type'); $app->get('/api/custom_fields/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':custom_fields'); $app->get('/api/saved_views/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':saved_views'); $app->post('/api/saved_views/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':post_saved_views'); $app->delete('/api/saved_views/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':delete_saved_views'); $app->patch('/api/saved_views/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_saved_views'); $app->put('/api/saved_views/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_saved_views'); $app->get('/api/storage_paths/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':storage_paths'); $app->get('/api/storage_paths/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':storage_path'); $app->post('/api/documents/post_document/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':post_document'); $app->get('/api/documents/{id}/preview/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':documents_preview'); $app->get('/api/documents/{id}/thumb/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':documents_thumb'); $app->get('/fetch/thumb/{id}', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':fetch_thumb'); $app->get('/api/documents/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':document'); $app->get('/api/documents/{id}/download/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':documents_download'); $app->get('/api/documents/{id}/metadata/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':documents_metadata'); $app->get('/api/documents/{id}/notes/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':documents_notes'); $app->get('/api/documents/{id}/history/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':documents_history'); $app->get('/fetch/doc/{id}', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':fetch_doc'); $app->patch('/api/documents/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':patch_documents'); $app->put('/api/documents/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':put_documents'); $app->delete('/api/documents/{id}/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':delete_documents'); $app->get('/api/search/autocomplete/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':autocomplete'); $app->get('/api/search/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':search'); $app->get('/api/ui_settings/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':ui_settings'); $app->get('/api/statstotal/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':statstotal'); $app->get('/api/statistics/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':statstotal'); $app->get('/api/status/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':status'); $app->get('/api/trash/', \SeedDMS_ExtPaperless_RestAPI_Controller::class.':trash'); return null; } /* }}} */ } /* }}} */