Commit bf298439297c2ad11b27df16ff85665fc7db2894
1 parent
eee5a712
Merged in from DEV trunk...
KTS-2505 "Database configuration issues cause horrible knowledgetree failure" Fixed. preventing early calls to the db Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-2457 "On search results page - move save search out of the collapsable area" Fixed. Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-2511 "short tag present on phpluceneindexer" Fixed. KTS-2398 "Java Lucene Server needs to send the highlighted results back more optimally" Updated. Had to modify the way results are returned from the php lucene indexer as well Reviewed By: Kevin Fourie Committed By: Conrad Vermeulen KTS-2398 "Java Lucene Server needs to send the highlighted results back more optimally" Updated. Also fixed optimise typo Reviewed By: Kevin Fourie Committed By: Conrad Vermeulen KTS-2512 "Search result object must return more fields to be used in templating and web services" Updated. added more fields to the result object Reviewed By: Kevin Fourie Committed By: Conrad Vermeulen KTS-673 "The search algorithm needs some work" Updated. dmsDefaults needs to move for internationalisation Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-2513 "Add params for host and port to OOo doc converter" Added. Committed By: Kevin Fourie Reviewed By: Conrad Vermeulen KTS-673 "The search algorithm needs some work" Updated. Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-2471 "create search2 dashets" Added. Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-2512 "Search result object must return more fields to be used in templating and web services" Updated. Added a status to string Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-2512 "Search result object must return more fields to be used in templating and web services" Updated. Added adminIsInAdminMode function Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-673 "The search algorithm needs some work" Updated. Proxies are required to unserialize() correctly Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-673 "The search algorithm needs some work" Updated. webservice search structures Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTC-248 "Disable commercial plugins on license expiry and display dashlet" Fixed. Changed delete user to deallocate the license. Changed isBaobabUser to isValid user on creating a session, so as not to lock the user out. Committed by: Megan Watson Reviewed by: Conrad Vermeulen KTS-673 "The search algorithm needs some work" Updated. Updated Lucene Server Committed By: Conrad Vermeulen Reviewed By: Kevin Fourie KTS-2251 "Characters in the names of Folders and files appear as question marks and squares in the crumbtrail." Fixed Question marks are no longer displayed on truncated bread crumb details. Committed By: Jonathan Byrne Reviewed By: Jalaloedien Abrahams BBS-298 "Mac OS X Webdav as 3rd Party Client" In Progress. Root folder parent_id was changed to null. Changed the sql to use is_null instead of =0. Affects all webdav, not only Mac. Committed By: Megan Watson Reviewed By: Conrad Vermeulen KTS-2430 "Create proper init script for stack install" Corrected port number. Committed By: Kevin Fourie Reviewed By: Conrad Vermeulen KTS-2519 "Move periodic scripts out of web tree to install root" In Progess. Committed By: Kevin Fourie Reviewed By: Conrad Vermeulen git-svn-id: https://kt-dms.svn.sourceforge.net/svnroot/kt-dms/STABLE/trunk@7418 c91229c3-7414-0410-bfa2-8a42b809f60b
Showing
26 changed files
with
295 additions
and
419 deletions
bin/diagnoseIndexing.bat deleted
bin/diagnoseIndexing.sh deleted
| 1 | -#!/bin/sh | |
| 2 | - | |
| 3 | -# SETUP PATH TO FIND PHP | |
| 4 | -PATH=$PATH:../../php/bin:../../php: | |
| 5 | - | |
| 6 | -# WORK OUT DIRECTORIES | |
| 7 | -USER_DIR=`pwd` | |
| 8 | -SCRIPT_DIR=$USER_DIR/`dirname $0` | |
| 9 | -PHP_SCRIPT_DIR=$SCRIPT_DIR/../search2/indexing/bin | |
| 10 | - | |
| 11 | -# EXECUTE SCRIPT IN THE SCRIPT DIRECTORY | |
| 12 | -cd $PHP_SCRIPT_DIR | |
| 13 | -php -Cq diagnose.php | |
| 14 | 0 | \ No newline at end of file |
bin/indexMigrationTask.bat deleted
bin/indexMigrationTask.sh deleted
| 1 | -#!/bin/sh | |
| 2 | - | |
| 3 | -# SETUP PATH TO FIND PHP | |
| 4 | -PATH=$PATH:../../php/bin:../../php: | |
| 5 | - | |
| 6 | -# WORK OUT DIRECTORIES | |
| 7 | -USER_DIR=`pwd` | |
| 8 | -SCRIPT_DIR=$USER_DIR/`dirname $0` | |
| 9 | -PHP_SCRIPT_DIR=$SCRIPT_DIR/../search2/indexing/bin | |
| 10 | - | |
| 11 | -# EXECUTE SCRIPT IN THE SCRIPT DIRECTORY | |
| 12 | -cd $PHP_SCRIPT_DIR | |
| 13 | -php -Cq cronMigration.php | |
| 14 | 0 | \ No newline at end of file |
bin/indexingTask.bat deleted
bin/indexingTask.sh deleted
| 1 | -#!/bin/sh | |
| 2 | - | |
| 3 | -# SETUP PATH TO FIND PHP | |
| 4 | -PATH=$PATH:../../php/bin:../../php: | |
| 5 | - | |
| 6 | -# WORK OUT DIRECTORIES | |
| 7 | -USER_DIR=`pwd` | |
| 8 | -SCRIPT_DIR=$USER_DIR/`dirname $0` | |
| 9 | -PHP_SCRIPT_DIR=$SCRIPT_DIR/../search2/indexing/bin | |
| 10 | - | |
| 11 | -# EXECUTE SCRIPT IN THE SCRIPT DIRECTORY | |
| 12 | -cd $PHP_SCRIPT_DIR | |
| 13 | -php -Cq cronIndexer.php | |
| 14 | 0 | \ No newline at end of file |
bin/luceneserver/ktlucene.jar
No preview for this file type
bin/openoffice/DocumentConverter.py
| ... | ... | @@ -7,7 +7,9 @@ |
| 7 | 7 | # Copyright (C) 2007 Mirko Nasato <mirko@artofsolving.com> |
| 8 | 8 | # Licensed under the GNU LGPL v2.1 - http://www.gnu.org/licenses/lgpl.html |
| 9 | 9 | # |
| 10 | -DEFAULT_OPENOFFICE_PORT = 8100 | |
| 10 | +# Modified by Kevin Fourie <kevin@knowledgetree.com> - 2007-10-18 | |
| 11 | + | |
| 12 | +#DEFAULT_OPENOFFICE_PORT = 8100 | |
| 11 | 13 | |
| 12 | 14 | import uno |
| 13 | 15 | from os.path import abspath, splitext |
| ... | ... | @@ -78,11 +80,11 @@ def _unoProps(**args): |
| 78 | 80 | |
| 79 | 81 | class DocumentConverter: |
| 80 | 82 | |
| 81 | - def __init__(self, port=DEFAULT_OPENOFFICE_PORT): | |
| 83 | + def __init__(self, host=argv[3], port=argv[4]): | |
| 82 | 84 | localContext = uno.getComponentContext() |
| 83 | 85 | resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext) |
| 84 | 86 | try: |
| 85 | - context = resolver.resolve("uno:socket,host=localhost,port=%s;urp;StarOffice.ComponentContext" % port) | |
| 87 | + context = resolver.resolve("uno:socket,host=%s,port=%s;urp;StarOffice.ComponentContext" % (host, port)) | |
| 86 | 88 | except NoConnectException: |
| 87 | 89 | raise DocumentConversionException, "failed to connect to OpenOffice.org on port %s" % port |
| 88 | 90 | self.desktop = context.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", context) |
| ... | ... | @@ -127,7 +129,7 @@ if __name__ == "__main__": |
| 127 | 129 | from sys import argv, exit |
| 128 | 130 | |
| 129 | 131 | if len(argv) < 3: |
| 130 | - print "USAGE: " + argv[0] + " <input-file> <output-file>" | |
| 132 | + print "USAGE: " + argv[0] + " <input-file> <output-file> <host> <port>" | |
| 131 | 133 | exit(255) |
| 132 | 134 | |
| 133 | 135 | try: | ... | ... |
bin/optimizeIndexes.bat deleted
bin/optimizeIndexes.sh deleted
| 1 | -#!/bin/sh | |
| 2 | - | |
| 3 | -# SETUP PATH TO FIND PHP | |
| 4 | -PATH=$PATH:../../php/bin:../../php: | |
| 5 | - | |
| 6 | -# WORK OUT DIRECTORIES | |
| 7 | -USER_DIR=`pwd` | |
| 8 | -SCRIPT_DIR=$USER_DIR/`dirname $0` | |
| 9 | -PHP_SCRIPT_DIR=$SCRIPT_DIR/../search2/indexing/bin | |
| 10 | - | |
| 11 | -# EXECUTE SCRIPT IN THE SCRIPT DIRECTORY | |
| 12 | -cd $PHP_SCRIPT_DIR | |
| 13 | -php -Cq optimise.php | |
| 14 | 0 | \ No newline at end of file |
examples/linux/init/dmsctl.sh
| ... | ... | @@ -43,7 +43,7 @@ XVFB_STATUS="" |
| 43 | 43 | SOFFICE_PATH="$INSTALL_PATH/openoffice/program" |
| 44 | 44 | SOFFICE_PIDFILE=$INSTALL_PATH/openoffice/soffice.bin.pid |
| 45 | 45 | SOFFICE_PID="" |
| 46 | -SOFFICE_PORT="8001" | |
| 46 | +SOFFICE_PORT="8100" | |
| 47 | 47 | SOFFICEBIN=$INSTALL_PATH/openoffice/program/soffice.bin |
| 48 | 48 | SOFFICE="$SOFFICEBIN -nofirststartwizard -nologo -headless -display :$VDISPLAY -accept=socket,host=localhost,port=$SOFFICE_PORT;urp;StarOffice.ServiceManager" |
| 49 | 49 | SOFFICE_STATUS="" | ... | ... |
ktwebdav/lib/KTWebDAVServer.inc.php
| ... | ... | @@ -1105,8 +1105,13 @@ class KTWebDAVServer extends HTTP_WebDAV_Server |
| 1105 | 1105 | continue; |
| 1106 | 1106 | } |
| 1107 | 1107 | // FIXME: Direct database access |
| 1108 | - $sQuery = "SELECT id FROM folders WHERE parent_id = ? AND name = ?"; | |
| 1109 | - $aParams = array($iFolderID, $sFolderName); | |
| 1108 | + if($iFolderID == 0){ | |
| 1109 | + $sQuery = "SELECT id FROM folders WHERE parent_id is null AND name = ?"; | |
| 1110 | + $aParams = array($sFolderName); | |
| 1111 | + }else{ | |
| 1112 | + $sQuery = "SELECT id FROM folders WHERE parent_id = ? AND name = ?"; | |
| 1113 | + $aParams = array($iFolderID, $sFolderName); | |
| 1114 | + } | |
| 1110 | 1115 | $id = DBUtil::getOneResultKey(array($sQuery, $aParams), 'id'); |
| 1111 | 1116 | if (PEAR::isError($id)) { |
| 1112 | 1117 | $this->ktwebdavLog("A DB error occurred in _folderOrDocument", 'info', true); | ... | ... |
ktwebservice/webservice.php
| ... | ... | @@ -195,16 +195,26 @@ class KTWebService |
| 195 | 195 | $this->__typedef["{urn:$this->namespace}kt_search_result_item"] = |
| 196 | 196 | array( |
| 197 | 197 | 'document_id' => 'int', |
| 198 | - 'title' => 'string', | |
| 199 | - 'rank' => 'float', | |
| 198 | + 'relevance' => 'float', | |
| 200 | 199 | 'text' => 'string', |
| 201 | - 'filesize' => 'int', | |
| 200 | + 'title' => 'string', | |
| 202 | 201 | 'fullpath' => 'string', |
| 202 | + 'filesize' => 'int', | |
| 203 | 203 | 'version' => 'string', |
| 204 | 204 | 'filename' => 'string', |
| 205 | - 'checked_out_user' => 'string', | |
| 206 | - 'is_available' => 'boolean', | |
| 207 | - 'workflow' => 'string' | |
| 205 | + 'folder_id' => 'int', | |
| 206 | + 'workflow' => 'string', | |
| 207 | + 'workflow_state' => 'string', | |
| 208 | + 'mime_type' => 'string', | |
| 209 | + 'owner' => 'string', | |
| 210 | + 'created_by' => 'string', | |
| 211 | + 'created_date' => 'string', | |
| 212 | + 'modified_by' => 'string', | |
| 213 | + 'modified_date' => 'string', | |
| 214 | + 'checked_out_by' => 'string', | |
| 215 | + 'checked_out_date' => 'string', | |
| 216 | + 'is_immutable' => 'bool', | |
| 217 | + 'status' => 'string', | |
| 208 | 218 | ); |
| 209 | 219 | |
| 210 | 220 | $this->__typedef["{urn:$this->namespace}kt_search_results"] = |
| ... | ... | @@ -3334,26 +3344,34 @@ class KTWebService |
| 3334 | 3344 | $results = array(); |
| 3335 | 3345 | foreach($rs as $hit) |
| 3336 | 3346 | { |
| 3337 | - if ($hit->IsLive) | |
| 3338 | - { | |
| 3339 | - | |
| 3340 | - $item = array( | |
| 3347 | + $item = array( | |
| 3341 | 3348 | 'document_id' => (int) $hit->DocumentID, |
| 3342 | 3349 | 'title' => (string) $hit->Title, |
| 3343 | - 'rank' => (float) $hit->Rank, | |
| 3350 | + 'relevance' => (float) $hit->Rank, | |
| 3344 | 3351 | 'text' => (string) $noText?'':$hit->Text, |
| 3345 | 3352 | 'filesize' => (int) $hit->Filesize, |
| 3346 | 3353 | 'fullpath' => (string) $hit->FullPath, |
| 3347 | 3354 | 'version' => (string) $hit->Version, |
| 3348 | 3355 | 'filename' => (string) $hit->Filename, |
| 3349 | - 'checked_out_user' => (string) $hit->CheckedOutUser, | |
| 3356 | + 'checked_out_by' => (string) $hit->CheckedOutUser, | |
| 3357 | + 'checked_out_date' => (string) $hit->DateCheckedOut, | |
| 3350 | 3358 | 'is_available' => (bool) $hit->IsAvailable, |
| 3351 | - 'workflow' => (string) $hit->Workflow | |
| 3359 | + 'workflow' => (string) $hit->Workflow, | |
| 3360 | + 'workflow_state' => (string) $hit->WorkflowState, | |
| 3361 | + 'folder_id' => (int) $hit->FolderId, | |
| 3362 | + 'mime_type' => (string) $hit->MimeType, | |
| 3363 | + 'modified_by' => (string) $hit->ModifiedBy, | |
| 3364 | + 'modified_date' => (string) $hit->DateModified, | |
| 3365 | + 'created_by' => (string) $hit->CreatedBy, | |
| 3366 | + 'created_date' => (string) $hit->DateCreated, | |
| 3367 | + 'owner' => (string) $hit->Owner, | |
| 3368 | + 'is_immutable'=> (bool) $hit->Immutable, | |
| 3369 | + 'status' => (string) $hit->Status | |
| 3352 | 3370 | ); |
| 3353 | 3371 | |
| 3354 | 3372 | $item = new SOAP_Value('item',"{urn:$this->namespace}kt_search_result_item", $item); |
| 3355 | 3373 | $results[] = $item; |
| 3356 | - } | |
| 3374 | + | |
| 3357 | 3375 | } |
| 3358 | 3376 | |
| 3359 | 3377 | $response['message'] = ''; | ... | ... |
lib/documentmanagement/Document.inc
| ... | ... | @@ -640,6 +640,25 @@ class Document { |
| 640 | 640 | return trim($aComment[1]); |
| 641 | 641 | } |
| 642 | 642 | |
| 643 | + static function getStatusString($statusid) | |
| 644 | + { | |
| 645 | + $statuses = array( | |
| 646 | + 1=>_kt('Live'), | |
| 647 | + 2=>_kt('Published'), | |
| 648 | + 3=>_kt('Deleted'), | |
| 649 | + 4=>_kt('Archived'), | |
| 650 | + 5=>_kt('Incomplete'), | |
| 651 | + 6=>_kt('Version Deleted') | |
| 652 | + ); | |
| 653 | + | |
| 654 | + if (array_key_exists($statusid, $statuses)) | |
| 655 | + { | |
| 656 | + return $statuses[$statusid]; | |
| 657 | + } | |
| 658 | + | |
| 659 | + return _kt('Unknown State'); | |
| 660 | + } | |
| 661 | + | |
| 643 | 662 | |
| 644 | 663 | |
| 645 | 664 | ... | ... |
lib/security/Permission.inc
| ... | ... | @@ -12,7 +12,7 @@ |
| 12 | 12 | * License Version 1.1.2 ("License"); You may not use this file except in |
| 13 | 13 | * compliance with the License. You may obtain a copy of the License at |
| 14 | 14 | * http://www.knowledgetree.com/KPL |
| 15 | - * | |
| 15 | + * | |
| 16 | 16 | * Software distributed under the License is distributed on an "AS IS" |
| 17 | 17 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. |
| 18 | 18 | * See the License for the specific language governing rights and |
| ... | ... | @@ -23,9 +23,9 @@ |
| 23 | 23 | * (ii) the KnowledgeTree copyright notice |
| 24 | 24 | * in the same form as they appear in the distribution. See the License for |
| 25 | 25 | * requirements. |
| 26 | - * | |
| 26 | + * | |
| 27 | 27 | * The Original Code is: KnowledgeTree Open Source |
| 28 | - * | |
| 28 | + * | |
| 29 | 29 | * The Initial Developer of the Original Code is The Jam Warehouse Software |
| 30 | 30 | * (Pty) Ltd, trading as KnowledgeTree. |
| 31 | 31 | * Portions created by The Jam Warehouse Software (Pty) Ltd are Copyright |
| ... | ... | @@ -38,15 +38,15 @@ require_once(KT_LIB_DIR . '/permissions/permission.inc.php'); |
| 38 | 38 | require_once(KT_LIB_DIR . '/permissions/permissionutil.inc.php'); |
| 39 | 39 | |
| 40 | 40 | class Permission { |
| 41 | - | |
| 41 | + | |
| 42 | 42 | /** |
| 43 | 43 | * Checks if the current user has write permission for a specific document. |
| 44 | 44 | * |
| 45 | 45 | * @param $oDocument Document to check |
| 46 | 46 | * |
| 47 | 47 | * @return boolean true if the current user has document write permission, false otherwise and set $_SESSION["errorMessage"] |
| 48 | - */ | |
| 49 | - function userHasDocumentWritePermission($oDocument) { | |
| 48 | + */ | |
| 49 | + function userHasDocumentWritePermission($oDocument) { | |
| 50 | 50 | $oUser = User::get($_SESSION["userID"]); |
| 51 | 51 | $oPermission = KTPermission::getByName('ktcore.permissions.write'); |
| 52 | 52 | |
| ... | ... | @@ -62,30 +62,30 @@ class Permission { |
| 62 | 62 | * |
| 63 | 63 | * @return boolean true if the user has document write permission, false otherwise and set $_SESSION["errorMessage"] |
| 64 | 64 | */ |
| 65 | - function userHasDocumentReadPermission($oDocument) { | |
| 65 | + function userHasDocumentReadPermission($oDocument) { | |
| 66 | 66 | $oUser = User::get($_SESSION["userID"]); |
| 67 | 67 | $oPermission = KTPermission::getByName('ktcore.permissions.read'); |
| 68 | 68 | |
| 69 | 69 | return KTPermissionUtil::userHasPermissionOnItem($oUser, |
| 70 | 70 | $oPermission, $oDocument); |
| 71 | 71 | } |
| 72 | - | |
| 72 | + | |
| 73 | 73 | /** |
| 74 | 74 | * Checks if the current user has write permission for a specific folder |
| 75 | - * | |
| 75 | + * | |
| 76 | 76 | * @param $oFolder Folder object to check |
| 77 | 77 | * |
| 78 | 78 | * @return boolean true if the user has folder write permission, false otherwise and set $_SESSION["errorMessage"] |
| 79 | 79 | */ |
| 80 | - function userHasFolderWritePermission($oFolder) { | |
| 80 | + function userHasFolderWritePermission($oFolder) { | |
| 81 | 81 | $oUser = User::get($_SESSION["userID"]); |
| 82 | 82 | $oPermission = KTPermission::getByName('ktcore.permissions.write'); |
| 83 | 83 | |
| 84 | 84 | return KTPermissionUtil::userHasPermissionOnItem($oUser, |
| 85 | 85 | $oPermission, $oFolder); |
| 86 | 86 | } |
| 87 | - | |
| 88 | - | |
| 87 | + | |
| 88 | + | |
| 89 | 89 | /** |
| 90 | 90 | * Checks if the current user has read permission for a specific folder |
| 91 | 91 | * |
| ... | ... | @@ -93,14 +93,14 @@ class Permission { |
| 93 | 93 | * |
| 94 | 94 | * @return boolean true if the user has folder write permission, false otherwise and set $_SESSION["errorMessage"] |
| 95 | 95 | */ |
| 96 | - function userHasFolderReadPermission($oFolder) { | |
| 96 | + function userHasFolderReadPermission($oFolder) { | |
| 97 | 97 | $oUser = User::get($_SESSION["userID"]); |
| 98 | 98 | $oPermission = KTPermission::getByName('ktcore.permissions.read'); |
| 99 | 99 | |
| 100 | 100 | return KTPermissionUtil::userHasPermissionOnItem($oUser, |
| 101 | 101 | $oPermission, $oFolder); |
| 102 | 102 | } |
| 103 | - | |
| 103 | + | |
| 104 | 104 | /** |
| 105 | 105 | * Check if the current user is a system administrator |
| 106 | 106 | * |
| ... | ... | @@ -123,7 +123,7 @@ class Permission { |
| 123 | 123 | } |
| 124 | 124 | return false; |
| 125 | 125 | } |
| 126 | - | |
| 126 | + | |
| 127 | 127 | function isUnitAdministratorForFolder($oUser, $oFolder) { |
| 128 | 128 | $oFolder =& KTUtil::getObject('Folder', $oFolder); |
| 129 | 129 | $oUser =& KTUtil::getObject('User', $oUser); |
| ... | ... | @@ -150,6 +150,21 @@ class Permission { |
| 150 | 150 | } |
| 151 | 151 | return false; |
| 152 | 152 | } |
| 153 | + | |
| 154 | + /** | |
| 155 | + * Tell us if the administrator is in admin mode | |
| 156 | + * | |
| 157 | + * @return bool | |
| 158 | + */ | |
| 159 | + static function adminIsInAdminMode() | |
| 160 | + { | |
| 161 | + if (!Permission::userIsSystemAdministrator()) | |
| 162 | + { | |
| 163 | + return false; | |
| 164 | + } | |
| 165 | + return isset($_SESSION['adminmode']) && ($_SESSION['adminmode']+0); | |
| 166 | + } | |
| 167 | + | |
| 153 | 168 | } |
| 154 | 169 | |
| 155 | 170 | ?> | ... | ... |
lib/session/Session.inc
| ... | ... | @@ -45,10 +45,12 @@ class Session { |
| 45 | 45 | |
| 46 | 46 | session_start(); |
| 47 | 47 | |
| 48 | + | |
| 49 | + // Don't need to lock a user out the web interface if KT Tools exists and has no license. | |
| 48 | 50 | if (KTPluginUtil::pluginIsActive('ktdms.wintools')) { |
| 49 | 51 | if (!$oUser->isAnonymous()) { |
| 50 | 52 | require_once(KT_DIR . '/plugins/wintools/baobabkeyutil.inc.php'); |
| 51 | - $res = BaobabKeyUtil::isBaobabUser($oUser); | |
| 53 | + $res = BaobabKeyUtil::isValidUser($oUser); | |
| 52 | 54 | if (PEAR::isError($res)) { |
| 53 | 55 | return $res; |
| 54 | 56 | } | ... | ... |
lib/users/User.inc
| ... | ... | @@ -8,7 +8,7 @@ |
| 8 | 8 | * License Version 1.1.2 ("License"); You may not use this file except in |
| 9 | 9 | * compliance with the License. You may obtain a copy of the License at |
| 10 | 10 | * http://www.knowledgetree.com/KPL |
| 11 | - * | |
| 11 | + * | |
| 12 | 12 | * Software distributed under the License is distributed on an "AS IS" |
| 13 | 13 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. |
| 14 | 14 | * See the License for the specific language governing rights and |
| ... | ... | @@ -19,9 +19,9 @@ |
| 19 | 19 | * (ii) the KnowledgeTree copyright notice |
| 20 | 20 | * in the same form as they appear in the distribution. See the License for |
| 21 | 21 | * requirements. |
| 22 | - * | |
| 22 | + * | |
| 23 | 23 | * The Original Code is: KnowledgeTree Open Source |
| 24 | - * | |
| 24 | + * | |
| 25 | 25 | * The Initial Developer of the Original Code is The Jam Warehouse Software |
| 26 | 26 | * (Pty) Ltd, trading as KnowledgeTree. |
| 27 | 27 | * Portions created by The Jam Warehouse Software (Pty) Ltd are Copyright |
| ... | ... | @@ -29,13 +29,13 @@ |
| 29 | 29 | * All Rights Reserved. |
| 30 | 30 | * Contributor( s): ______________________________________ |
| 31 | 31 | */ |
| 32 | - | |
| 33 | -require_once(KT_LIB_DIR . '/database/dbutil.inc'); | |
| 34 | -require_once(KT_LIB_DIR . '/ktentity.inc'); | |
| 32 | + | |
| 33 | +require_once(KT_LIB_DIR . '/database/dbutil.inc'); | |
| 34 | +require_once(KT_LIB_DIR . '/ktentity.inc'); | |
| 35 | 35 | require_once(KT_LIB_DIR . "/util/sanitize.inc"); |
| 36 | 36 | |
| 37 | 37 | define('ADMIN_USER_ID', 1); |
| 38 | - | |
| 38 | + | |
| 39 | 39 | class User extends KTEntity { |
| 40 | 40 | /** user's login name */ |
| 41 | 41 | var $sUserName; |
| ... | ... | @@ -106,14 +106,14 @@ class User extends KTEntity { |
| 106 | 106 | function _table() { |
| 107 | 107 | return KTUtil::getTableName("users"); |
| 108 | 108 | } |
| 109 | - | |
| 109 | + | |
| 110 | 110 | // STATIC |
| 111 | 111 | function _ktentityOptions() { |
| 112 | 112 | return array( |
| 113 | 113 | 'orderby' => 'name', |
| 114 | 114 | ); |
| 115 | 115 | } |
| 116 | - | |
| 116 | + | |
| 117 | 117 | |
| 118 | 118 | function getUserName() { return sanitizeForSQLtoHTML($this->sUserName); } |
| 119 | 119 | function setUserName($sNewValue) { $this->sUserName = sanitizeForSQL($sNewValue); } |
| ... | ... | @@ -169,11 +169,11 @@ class User extends KTEntity { |
| 169 | 169 | return 'dashboard-state-' . $this->getId(); |
| 170 | 170 | } |
| 171 | 171 | |
| 172 | - function getDashboardState() { | |
| 172 | + function getDashboardState() { | |
| 173 | 173 | return KTUtil::getSystemSetting($this->_getDashboardStateKey()); |
| 174 | 174 | } |
| 175 | 175 | |
| 176 | - function setDashboardState($mValue) { | |
| 176 | + function setDashboardState($mValue) { | |
| 177 | 177 | KTUtil::setSystemSetting($this->_getDashboardStateKey(), $mValue); |
| 178 | 178 | } |
| 179 | 179 | |
| ... | ... | @@ -182,9 +182,9 @@ class User extends KTEntity { |
| 182 | 182 | require_once(KT_DIR . "/thirdparty/pear/JSON.php"); |
| 183 | 183 | |
| 184 | 184 | $dashletRegistry = & KTDashletRegistry::getSingleton(); |
| 185 | - | |
| 185 | + | |
| 186 | 186 | $aDashlets = $dashletRegistry->getDashlets($this); |
| 187 | - | |
| 187 | + | |
| 188 | 188 | $oJSON = new Services_JSON(); |
| 189 | 189 | |
| 190 | 190 | $state = $this->getDashboardState(); |
| ... | ... | @@ -193,18 +193,18 @@ class User extends KTEntity { |
| 193 | 193 | if (!isset($dashlets->left)) $dashlets->left = array(); |
| 194 | 194 | if (!isset($dashlets->right)) $dashlets->right = array(); |
| 195 | 195 | $mergedlist = kt_array_merge($dashlets->left,$dashlets->right); |
| 196 | - | |
| 197 | - | |
| 196 | + | |
| 197 | + | |
| 198 | 198 | $knownlist = array(); |
| 199 | 199 | foreach($mergedlist as $dashlet) |
| 200 | 200 | { |
| 201 | 201 | array_push($knownlist,$dashlet->id); |
| 202 | 202 | } |
| 203 | 203 | $update=false; |
| 204 | - | |
| 204 | + | |
| 205 | 205 | //if (!isset($dashlets->left)) $dashlets->left=array(); |
| 206 | 206 | //if (!isset($dashlets->right)) $dashlets->right=array(); |
| 207 | - | |
| 207 | + | |
| 208 | 208 | $column=1; |
| 209 | 209 | foreach($aDashlets as $dashlet) |
| 210 | 210 | { |
| ... | ... | @@ -215,7 +215,7 @@ class User extends KTEntity { |
| 215 | 215 | $obj = new stdClass(); |
| 216 | 216 | $obj->id=$class; |
| 217 | 217 | $obj->state=0; |
| 218 | - | |
| 218 | + | |
| 219 | 219 | if ($column == 0) |
| 220 | 220 | array_push($dashlets->left,$obj); |
| 221 | 221 | else |
| ... | ... | @@ -223,19 +223,19 @@ class User extends KTEntity { |
| 223 | 223 | $update=true; |
| 224 | 224 | } |
| 225 | 225 | } |
| 226 | - | |
| 226 | + | |
| 227 | 227 | if ($update) |
| 228 | 228 | { |
| 229 | 229 | $state = $oJSON->encode($dashlets); |
| 230 | 230 | $this->setDashboardState($state); |
| 231 | 231 | } |
| 232 | 232 | } |
| 233 | - | |
| 234 | - | |
| 233 | + | |
| 234 | + | |
| 235 | 235 | function &get($iId) { |
| 236 | 236 | return KTEntityUtil::get('User', $iId); |
| 237 | 237 | } |
| 238 | - | |
| 238 | + | |
| 239 | 239 | /** |
| 240 | 240 | * update the datastore, without overwriting the password. |
| 241 | 241 | * |
| ... | ... | @@ -244,25 +244,25 @@ class User extends KTEntity { |
| 244 | 244 | function doLimitedUpdate() { |
| 245 | 245 | $sQuery = 'UPDATE ' . $this->_table() . ' SET '; |
| 246 | 246 | $aParams = array(); |
| 247 | - | |
| 247 | + | |
| 248 | 248 | $blacklist = array( |
| 249 | 249 | "sPassword" => 1, |
| 250 | 250 | ); |
| 251 | - | |
| 251 | + | |
| 252 | 252 | $aParts = array(); // quick workaround to make the join less hurtful. |
| 253 | - | |
| 253 | + | |
| 254 | 254 | foreach ($this->_aFieldToSelect as $attr => $column) { |
| 255 | 255 | if (!array_key_exists($attr, $blacklist)) { |
| 256 | 256 | $val = $this->$attr; |
| 257 | 257 | $aParts[] = $column . ' = ?'; |
| 258 | - $aParams[] = $val; | |
| 259 | - } | |
| 258 | + $aParams[] = $val; | |
| 259 | + } | |
| 260 | 260 | } |
| 261 | 261 | $sQuery .= join(', ', $aParts); |
| 262 | - | |
| 262 | + | |
| 263 | 263 | $sQuery .= ' WHERE id = ? '; |
| 264 | 264 | $aParams[] = $this->getId(); |
| 265 | - | |
| 265 | + | |
| 266 | 266 | $res = DBUtil::runQuery(array($sQuery, $aParams)); |
| 267 | 267 | |
| 268 | 268 | $group = sprintf("%s/%s", get_class($this), 'id'); |
| ... | ... | @@ -272,8 +272,8 @@ class User extends KTEntity { |
| 272 | 272 | |
| 273 | 273 | return $res; |
| 274 | 274 | } |
| 275 | - | |
| 276 | - | |
| 275 | + | |
| 276 | + | |
| 277 | 277 | /** |
| 278 | 278 | * Static function |
| 279 | 279 | * Get a list of users |
| ... | ... | @@ -329,7 +329,7 @@ class User extends KTEntity { |
| 329 | 329 | |
| 330 | 330 | $this->iId = $id; |
| 331 | 331 | } |
| 332 | - | |
| 332 | + | |
| 333 | 333 | /** Static function |
| 334 | 334 | * Gets the user's default top level folder for the current user |
| 335 | 335 | */ |
| ... | ... | @@ -343,7 +343,7 @@ class User extends KTEntity { |
| 343 | 343 | $oUnit =& Unit::get($iUnitId); |
| 344 | 344 | return $oUnit->getFolderId(); |
| 345 | 345 | } |
| 346 | - | |
| 346 | + | |
| 347 | 347 | function &createFromArray($aOptions) { return KTEntityUtil::createFromArray('User', $aOptions); } |
| 348 | 348 | function &getByUserName($sUserName, $aOptions = null) { |
| 349 | 349 | return KTEntityUtil::getBy('User', 'username', $sUserName, $aOptions); |
| ... | ... | @@ -385,7 +385,7 @@ class User extends KTEntity { |
| 385 | 385 | $aParams = array(false); |
| 386 | 386 | return DBUtil::getOneResultKey(array($sQuery, $aParams), 'number'); |
| 387 | 387 | } |
| 388 | - | |
| 388 | + | |
| 389 | 389 | function isAnonymous() { return $this->iId == -2; } |
| 390 | 390 | |
| 391 | 391 | function disable() { |
| ... | ... | @@ -397,7 +397,7 @@ class User extends KTEntity { |
| 397 | 397 | } |
| 398 | 398 | return; |
| 399 | 399 | } |
| 400 | - | |
| 400 | + | |
| 401 | 401 | function enable() { |
| 402 | 402 | $this->setDisabled(0); |
| 403 | 403 | $this->update(); |
| ... | ... | @@ -406,7 +406,7 @@ class User extends KTEntity { |
| 406 | 406 | BaobabKeyUtil::allocateUser($this); |
| 407 | 407 | } |
| 408 | 408 | return; |
| 409 | - } | |
| 409 | + } | |
| 410 | 410 | |
| 411 | 411 | function create() { |
| 412 | 412 | if (KTPluginUtil::pluginIsActive('ktdms.wintools')) { |
| ... | ... | @@ -418,7 +418,7 @@ class User extends KTEntity { |
| 418 | 418 | } |
| 419 | 419 | return parent::create(); |
| 420 | 420 | } |
| 421 | - | |
| 421 | + | |
| 422 | 422 | function delete() |
| 423 | 423 | { |
| 424 | 424 | $this->setDisabled(2); |
| ... | ... | @@ -429,7 +429,7 @@ class User extends KTEntity { |
| 429 | 429 | $tempUserID = $this->iId; |
| 430 | 430 | $DeletedUsername = 'kt_deleted_'.$tempUsername.'_'.$tempUserID; |
| 431 | 431 | $this->setUsername($DeletedUsername); |
| 432 | - | |
| 432 | + | |
| 433 | 433 | //nullify all authentication_xxx fields |
| 434 | 434 | $this->setAuthenticationSourceId(null); |
| 435 | 435 | $this->setAuthenticationDetails(null); |
| ... | ... | @@ -440,13 +440,13 @@ class User extends KTEntity { |
| 440 | 440 | $this->setAuthenticationDetailsDate2(null); |
| 441 | 441 | $this->setAuthenticationDetailsBool1(null); |
| 442 | 442 | $this->setAuthenticationDetailsBool2(null); |
| 443 | - | |
| 443 | + | |
| 444 | 444 | $this->update(); |
| 445 | 445 | if (KTPluginUtil::pluginIsActive('ktdms.wintools')) { |
| 446 | 446 | require_once(KT_DIR . '/plugins/wintools/baobabkeyutil.inc.php'); |
| 447 | - BaobabKeyUtil::allocateUser($this); | |
| 447 | + BaobabKeyUtil::deallocateUser($this); | |
| 448 | 448 | } |
| 449 | 449 | return; |
| 450 | - | |
| 450 | + | |
| 451 | 451 | } |
| 452 | 452 | } | ... | ... |
plugins/search2/ExternalDashlet.php
| ... | ... | @@ -97,7 +97,7 @@ class ExternalResourceStatusDashlet extends KTBaseDashlet |
| 97 | 97 | |
| 98 | 98 | function is_active($oUser) |
| 99 | 99 | { |
| 100 | - if (!Permission::userIsSystemAdministrator($oUser)) | |
| 100 | + if (!Permission::userIsSystemAdministrator($oUser->getId())) | |
| 101 | 101 | { |
| 102 | 102 | return false; |
| 103 | 103 | } | ... | ... |
search2.php
| ... | ... | @@ -9,6 +9,7 @@ require_once(KT_LIB_DIR . "/dispatcher.inc.php"); |
| 9 | 9 | require_once(KT_LIB_DIR . "/widgets/forms.inc.php"); |
| 10 | 10 | require_once(KT_LIB_DIR . "/actions/bulkaction.php"); |
| 11 | 11 | require_once(KT_DIR . '/search2/search/search.inc.php'); |
| 12 | +require_once(KT_LIB_DIR . '/documentmanagement/Document.inc'); | |
| 12 | 13 | |
| 13 | 14 | |
| 14 | 15 | class SearchDispatcher extends KTStandardDispatcher { |
| ... | ... | @@ -162,6 +163,10 @@ class SearchDispatcher extends KTStandardDispatcher { |
| 162 | 163 | $oTemplating =& KTTemplating::getSingleton(); |
| 163 | 164 | $oTemplate = $oTemplating->loadTemplate("ktcore/search2/search_results"); |
| 164 | 165 | |
| 166 | + KTEntityUtil::_proxyCreate('KTDocumentContentVersion','KTDocumentContentVersionProxy'); | |
| 167 | + KTEntityUtil::_proxyCreate('KTDocumentCore','KTDocumentCoreProxy'); | |
| 168 | + KTEntityUtil::_proxyCreate('KTDocumentMetadataVersion','KTDocumentMetadataVersionProxy'); | |
| 169 | + | |
| 165 | 170 | $results = unserialize($_SESSION['search2_results']); |
| 166 | 171 | |
| 167 | 172 | if (!is_array($results) || count($results) == 0) | ... | ... |
search2/indexing/bin/recreateIndex.php
| ... | ... | @@ -10,6 +10,8 @@ |
| 10 | 10 | */ |
| 11 | 11 | |
| 12 | 12 | session_start(); |
| 13 | +require_once(realpath('../../../config/dmsDefaults.php')); | |
| 14 | + | |
| 13 | 15 | print _kt("Recreate Lucene index") . "...\n"; |
| 14 | 16 | |
| 15 | 17 | $sure=false; |
| ... | ... | @@ -39,7 +41,6 @@ if (!$sure) |
| 39 | 41 | } |
| 40 | 42 | |
| 41 | 43 | |
| 42 | -require_once(realpath('../../../config/dmsDefaults.php')); | |
| 43 | 44 | |
| 44 | 45 | $config = KTConfig::getSingleton(); |
| 45 | 46 | $indexer = $config->get('indexer/coreClass'); | ... | ... |
search2/indexing/indexerCore.inc.php
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | require_once('indexing/extractorCore.inc.php'); |
| 4 | 4 | |
| 5 | 5 | |
| 6 | -class MatchResult | |
| 6 | +class QueryResultItem | |
| 7 | 7 | { |
| 8 | 8 | protected $document_id; |
| 9 | 9 | protected $title; |
| ... | ... | @@ -13,15 +13,27 @@ class MatchResult |
| 13 | 13 | protected $fullpath; |
| 14 | 14 | protected $live; |
| 15 | 15 | protected $version; |
| 16 | + protected $mimeType; | |
| 16 | 17 | protected $filename; |
| 17 | 18 | protected $thumbnail; // TODO: if not null, gui can display a thumbnail |
| 18 | 19 | protected $viewer; // TODO: if not null, a viewer can be used to view the document |
| 19 | 20 | protected $document; |
| 20 | - protected $checkoutuser; | |
| 21 | - protected $workflowstate; | |
| 21 | + protected $checkedOutUser; | |
| 22 | + protected $dateCheckedout; | |
| 23 | + protected $workflowState; | |
| 22 | 24 | protected $workflow; |
| 23 | - | |
| 24 | - public function __construct($document_id, $rank, $title, $text) | |
| 25 | + protected $modifiedBy; | |
| 26 | + protected $dateModified; | |
| 27 | + protected $createdBy; | |
| 28 | + protected $dateCreated; | |
| 29 | + protected $owner; | |
| 30 | + protected $immutable; | |
| 31 | + protected $deleted; | |
| 32 | + protected $status; | |
| 33 | + protected $folderId; | |
| 34 | + | |
| 35 | + | |
| 36 | + public function __construct($document_id, $rank=null, $title=null, $text=null) | |
| 25 | 37 | { |
| 26 | 38 | $this->document_id=$document_id; |
| 27 | 39 | $this->rank= $rank; |
| ... | ... | @@ -48,17 +60,25 @@ class MatchResult |
| 48 | 60 | private function loadDocumentInfo() |
| 49 | 61 | { |
| 50 | 62 | $sql = "SELECT |
| 51 | - f.full_path, f.name, dcv.size as filesize, dcv.major_version, | |
| 52 | - dcv.minor_version, dcv.filename, cou.name as checkoutuser, w.human_name as workflow, ws.human_name as workflowstate | |
| 63 | + f.folder_id, f.full_path, f.name, dcv.size as filesize, dcv.major_version, | |
| 64 | + dcv.minor_version, dcv.filename, cou.name as checkoutuser, w.human_name as workflow, ws.human_name as workflowstate, | |
| 65 | + mt.mimetypes as mimetype, md.mime_doc as mimedoc, d.checkedout, mbu.name as modifiedbyuser, d.modified, | |
| 66 | + cbu.name as createdbyuser, ou.name as owneruser, d.immutable, d.status_id, d.created | |
| 53 | 67 | |
| 54 | 68 | FROM |
| 55 | 69 | documents d |
| 56 | 70 | INNER JOIN document_metadata_version dmv ON d.metadata_version_id = dmv.id |
| 57 | 71 | INNER JOIN document_content_version dcv ON dmv.content_version_id = dcv.id |
| 72 | + INNER JOIN mime_types mt ON dcv.mime_id=mt.id | |
| 58 | 73 | LEFT JOIN folders f ON f.id=d.folder_id |
| 59 | 74 | LEFT JOIN users cou ON d.checked_out_user_id=cou.id |
| 60 | 75 | LEFT JOIN workflows w ON dmv.workflow_id=w.id |
| 61 | 76 | LEFT JOIN workflow_states ws ON dmv.workflow_state_id = ws.id |
| 77 | + LEFT JOIN mime_documents md ON mt.mime_document_id = md.id | |
| 78 | + LEFT JOIN users mbu ON d.modified_user_id=mbu.id | |
| 79 | + LEFT JOIN users cbu ON d.creator_id=cbu.id | |
| 80 | + LEFT JOIN users ou ON d.owner_id=ou.id | |
| 81 | + | |
| 62 | 82 | WHERE |
| 63 | 83 | d.id=$this->document_id"; |
| 64 | 84 | |
| ... | ... | @@ -81,65 +101,86 @@ class MatchResult |
| 81 | 101 | if (substr($this->fullpath,0,1) == '/') $this->fullpath = substr($this->fullpath,1); |
| 82 | 102 | } |
| 83 | 103 | |
| 84 | - | |
| 85 | - $this->filesize = $result['filesize'] + 0; | |
| 86 | - | |
| 87 | - if ($this->filesize > 1024 * 1024 * 1024) | |
| 88 | - { | |
| 89 | - $this->filesize = floor($this->filesize / (1024 * 1024 * 1024)) . 'g'; | |
| 90 | - } | |
| 91 | - elseif ($this->filesize > 1024 * 1024) | |
| 92 | - { | |
| 93 | - $this->filesize = floor($this->filesize / (1024 * 1024)) . 'm'; | |
| 94 | - } | |
| 95 | - elseif ($this->filesize > 1024) | |
| 96 | - { | |
| 97 | - $this->filesize = floor($this->filesize / (1024)) . 'k'; | |
| 98 | - } | |
| 99 | - else | |
| 100 | - { | |
| 101 | - $this->filesize .= 'b'; | |
| 102 | - } | |
| 104 | + $this->filesize = KTUtil::filesizeToString($result['filesize']); | |
| 103 | 105 | |
| 104 | 106 | $this->version = $result['major_version'] . '.' . $result['minor_version']; |
| 105 | 107 | $this->filename=$result['filename']; |
| 106 | - $this->checkoutuser = $result['checkoutuser']; | |
| 108 | + $this->checkedOutUser = $result['checkoutuser']; | |
| 107 | 109 | $this->workflow = $result['workflow']; |
| 108 | - $this->workflowstate = $result['workflowstate']; | |
| 110 | + $this->workflowState = $result['workflowstate']; | |
| 109 | 111 | |
| 110 | - } | |
| 111 | 112 | |
| 113 | + $this->mimeType = $result['mimetype']; | |
| 114 | + $this->dateCheckedout = $result['checkedout']; | |
| 112 | 115 | |
| 116 | + $this->modifiedBy = $result['modifiedbyuser']; | |
| 117 | + $this->dateModified = $result['modified']; | |
| 118 | + $this->createdBy = $result['createdbyuser']; | |
| 119 | + $this->dateCreated = $result['created']; | |
| 120 | + | |
| 121 | + $this->owner = $result['owneruser']; | |
| 122 | + $this->immutable = ($result['immutable'] + 0)?_kt('Immutable'):''; | |
| 123 | + $this->status = Document::getStatusString($result['status_id']); | |
| 124 | + $this->folderId = $result['folder_id']; | |
| 125 | + | |
| 126 | + } | |
| 113 | 127 | |
| 114 | 128 | protected function __get($property) |
| 115 | 129 | { |
| 116 | 130 | switch($property) |
| 117 | 131 | { |
| 118 | - case 'DocumentID': return $this->document_id; | |
| 119 | - case 'Rank': return $this->rank; | |
| 120 | - case 'Text': return $this->text; | |
| 121 | - case 'Title': return $this->title; | |
| 122 | - case 'FullPath': return $this->fullpath; | |
| 123 | - case 'IsLive': return $this->live; | |
| 124 | - case 'Filesize': return $this->filesize; | |
| 125 | - case 'Version': return $this->version; | |
| 126 | - case 'Filename': return $this->filename; | |
| 132 | + case null: return ''; | |
| 133 | + case 'DocumentID': return (int) $this->document_id; | |
| 134 | + case 'Relevance': | |
| 135 | + case 'Rank': return (float) $this->rank; | |
| 136 | + case 'Text': return (string) $this->text; | |
| 137 | + case 'Title': return (string) $this->title; | |
| 138 | + case 'FullPath': return (string) $this->fullpath; | |
| 139 | + case 'IsLive': return (bool) $this->live; | |
| 140 | + case 'Filesize': return (int) $this->filesize; | |
| 141 | + case 'Version': return (string) $this->version; | |
| 142 | + case 'Filename': return (int)$this->filename; | |
| 143 | + case 'FolderId': return (int)$this->folderId; | |
| 127 | 144 | case 'Document': |
| 128 | 145 | if (is_null($this->document)) |
| 146 | + { | |
| 129 | 147 | $this->document = Document::get($this->document_id); |
| 148 | + } | |
| 130 | 149 | return $this->document; |
| 131 | 150 | case 'IsAvailable': |
| 132 | 151 | return $this->Document->isLive(); |
| 133 | - | |
| 134 | 152 | case 'CheckedOutUser': |
| 135 | - return $this->checkoutuser; | |
| 153 | + return (string) $this->checkedOutUser; | |
| 154 | + case 'WorkflowOnly': | |
| 155 | + return (string)$this->workflow; | |
| 156 | + case 'WorkflowStateOnly': | |
| 157 | + return (string)$this->workflowState; | |
| 136 | 158 | case 'Workflow': |
| 137 | 159 | if (is_null($this->workflow)) |
| 138 | 160 | { |
| 139 | 161 | return ''; |
| 140 | 162 | } |
| 141 | - return "$this->workflow - $this->workflowstate"; | |
| 142 | - case null: break; | |
| 163 | + return "$this->workflow - $this->workflowState"; | |
| 164 | + case 'MimeType': | |
| 165 | + return (string) $this->mimeType; | |
| 166 | + case 'DateCheckedOut': | |
| 167 | + return (string) $this->dateCheckedout; | |
| 168 | + case 'ModifiedBy': | |
| 169 | + return (string) $this->modifiedBy; | |
| 170 | + case 'DateModified': | |
| 171 | + return (string) $this->dateModified; | |
| 172 | + case 'CreatedBy': | |
| 173 | + return (string) $this->createdBy; | |
| 174 | + case 'DateCreated': | |
| 175 | + return (string) $this->dateCreated; | |
| 176 | + case 'Owner': | |
| 177 | + return (string) $this->owner; | |
| 178 | + case 'Immutable': | |
| 179 | + return (bool) $this->immutable; | |
| 180 | + case 'Status': | |
| 181 | + return $this->status; | |
| 182 | + case 'CanBeReadByUser': | |
| 183 | + return (bool) $this->live && (Permission::userHasDocumentReadPermission($this->Document) || Permission::adminIsInAdminMode()); | |
| 143 | 184 | default: |
| 144 | 185 | throw new Exception("Unknown property '$property' to get on MatchResult"); |
| 145 | 186 | } |
| ... | ... | @@ -151,6 +192,7 @@ class MatchResult |
| 151 | 192 | switch($property) |
| 152 | 193 | { |
| 153 | 194 | case 'Rank': $this->rank = number_format($value,2,'.',','); break; |
| 195 | + case 'Title': $this->title = $value; break; | |
| 154 | 196 | case 'Text': $this->text = $value; break; |
| 155 | 197 | default: |
| 156 | 198 | throw new Exception("Unknown property '$property' to set on MatchResult"); |
| ... | ... | @@ -166,35 +208,6 @@ function MatchResultCompare($a, $b) |
| 166 | 208 | return ($a->Rank < $b->Rank) ? -1 : 1; |
| 167 | 209 | } |
| 168 | 210 | |
| 169 | -class QueryResultItem extends MatchResult | |
| 170 | -{ | |
| 171 | - protected $discussion; | |
| 172 | - | |
| 173 | - public function __construct($document_id, $rank, $title, $text, $discussion) | |
| 174 | - { | |
| 175 | - parent::__construct($document_id, $rank, $title, $text); | |
| 176 | - $this->discussion=$discussion; | |
| 177 | - } | |
| 178 | - | |
| 179 | - protected function __isset($property) | |
| 180 | - { | |
| 181 | - switch($property) | |
| 182 | - { | |
| 183 | - case 'Discussion': return isset($this->discussion); | |
| 184 | - default: return parent::__isset($property); | |
| 185 | - } | |
| 186 | - } | |
| 187 | - | |
| 188 | - protected function __get($property) | |
| 189 | - { | |
| 190 | - switch($property) | |
| 191 | - { | |
| 192 | - case 'Discussion': return $this->discussion; | |
| 193 | - default: return parent::__get($property); | |
| 194 | - } | |
| 195 | - } | |
| 196 | -} | |
| 197 | - | |
| 198 | 211 | abstract class Indexer |
| 199 | 212 | { |
| 200 | 213 | /** | ... | ... |
search2/indexing/indexers/JavaXMLRPCLuceneIndexer.inc.php
| ... | ... | @@ -100,7 +100,7 @@ class JavaXMLRPCLuceneIndexer extends Indexer |
| 100 | 100 | public function optimise() |
| 101 | 101 | { |
| 102 | 102 | parent::optimise(); |
| 103 | - $this->lucene->optimize(); | |
| 103 | + $this->lucene->optimise(); | |
| 104 | 104 | } |
| 105 | 105 | |
| 106 | 106 | /** |
| ... | ... | @@ -128,18 +128,20 @@ class JavaXMLRPCLuceneIndexer extends Indexer |
| 128 | 128 | { |
| 129 | 129 | foreach ($hits as $hit) |
| 130 | 130 | { |
| 131 | - | |
| 132 | - | |
| 133 | 131 | $document_id = $hit->DocumentID; |
| 134 | - $content = $hit->Text; | |
| 135 | - $discussion = $hit->Title; //TODO: fix to be discussion. lucen server is not returning discussion text as well.. | |
| 136 | - $title = $hit->Title; | |
| 137 | - $score = $hit->Rank; | |
| 138 | 132 | |
| 139 | 133 | // avoid adding duplicates. If it is in already, it has higher priority. |
| 140 | 134 | if (!array_key_exists($document_id, $results) || $score > $results[$document_id]->Score) |
| 141 | 135 | { |
| 142 | - $results[$document_id] = new QueryResultItem($document_id, $score, $title, $content, $discussion); | |
| 136 | + $item = new QueryResultItem($document_id); | |
| 137 | + $item->Title = $hit->Title; | |
| 138 | + $item->Text = $hit->Content; | |
| 139 | + $item->Rank = $hit->Rank; | |
| 140 | + | |
| 141 | + if ($item->CanBeReadByUser) | |
| 142 | + { | |
| 143 | + $results[$document_id] = $item; | |
| 144 | + } | |
| 143 | 145 | } |
| 144 | 146 | } |
| 145 | 147 | } | ... | ... |
search2/indexing/indexers/PHPLuceneIndexer.inc.php
| 1 | -<? | |
| 1 | +<?php | |
| 2 | 2 | |
| 3 | 3 | require_once 'Zend/Search/Lucene.php'; |
| 4 | 4 | |
| ... | ... | @@ -179,6 +179,8 @@ class PHPLuceneIndexer extends Indexer |
| 179 | 179 | public function query($query) |
| 180 | 180 | { |
| 181 | 181 | $results = array(); |
| 182 | + $queryDiscussion = stripos($query,'discussion') !== false; | |
| 183 | + $queryContent = stripos($query,'content') !== false; | |
| 182 | 184 | $query = Zend_Search_Lucene_Search_QueryParser::parse($query); |
| 183 | 185 | |
| 184 | 186 | $hits = $this->lucene->find($query); |
| ... | ... | @@ -187,15 +189,30 @@ class PHPLuceneIndexer extends Indexer |
| 187 | 189 | $document = $hit->getDocument(); |
| 188 | 190 | |
| 189 | 191 | $document_id = PHPLuceneIndexer::stringToLong($document->DocumentID); |
| 190 | - $content = $document->Content ; | |
| 191 | - $discussion = $document->Discussion ; | |
| 192 | + | |
| 193 | + $coreText = ''; | |
| 194 | + if ($queryContent) | |
| 195 | + { | |
| 196 | + $coreText .= $document->Content; | |
| 197 | + } | |
| 198 | + if ($queryDiscussion) | |
| 199 | + { | |
| 200 | + $coreText .= $document->Discussion; | |
| 201 | + } | |
| 202 | + | |
| 203 | + $content = $query->highlightMatches($coreText); | |
| 204 | + | |
| 192 | 205 | $title = $document->Title; |
| 193 | 206 | $score = $hit->score; |
| 194 | 207 | |
| 195 | 208 | // avoid adding duplicates. If it is in already, it has higher priority. |
| 196 | 209 | if (!array_key_exists($document_id, $results) || $score > $results[$document_id]->Score) |
| 197 | 210 | { |
| 198 | - $results[$document_id] = new QueryResultItem($document_id, $score, $title, $content, $discussion); | |
| 211 | + $item = new QueryResultItem($document_id, $score, $title, $content); | |
| 212 | + if ($item->CanBeReadByUser) | |
| 213 | + { | |
| 214 | + $results[$document_id] = $item; | |
| 215 | + } | |
| 199 | 216 | } |
| 200 | 217 | } |
| 201 | 218 | return $results; | ... | ... |
search2/search/expr.inc.php
| ... | ... | @@ -847,16 +847,7 @@ class TextQueryBuilder implements QueryBuilder |
| 847 | 847 | $init = $result->Rank; |
| 848 | 848 | $score=0; |
| 849 | 849 | $ranker = RankManager::get(); |
| 850 | - $discussion = $result->Discussion; | |
| 851 | - if (!empty($discussion)) | |
| 852 | - { | |
| 853 | - $score += $init *$ranker->scoreField('Discussion', 'S'); | |
| 854 | - } | |
| 855 | - else | |
| 856 | - { | |
| 857 | - $score += $init *$ranker->scoreField('DocumentText', 'S'); | |
| 858 | - | |
| 859 | - } | |
| 850 | + $score += $init *$ranker->scoreField('DocumentText', 'S'); | |
| 860 | 851 | return $score; |
| 861 | 852 | } |
| 862 | 853 | |
| ... | ... | @@ -865,142 +856,11 @@ class TextQueryBuilder implements QueryBuilder |
| 865 | 856 | $this->query = $query; |
| 866 | 857 | } |
| 867 | 858 | |
| 868 | - private function extractText($word, $maxwords=40, $maxlen=512) | |
| 859 | + function getResultText($result) | |
| 869 | 860 | { |
| 870 | - $offset=stripos($this->text, $word); | |
| 871 | - | |
| 872 | - if ($offset === false) | |
| 873 | - { | |
| 874 | - return array(false, false); | |
| 875 | - } | |
| 876 | - | |
| 877 | - if ($offset == 0) | |
| 878 | - { | |
| 879 | - $startOffset = 0; | |
| 880 | - } | |
| 881 | - else | |
| 882 | - { | |
| 883 | - $text = substr($this->text, 0 , $offset); | |
| 884 | - | |
| 885 | - $lastsentence = strrpos($text, '.'); | |
| 886 | - if ($lastsentence === false) $lastsentence=0; | |
| 887 | - | |
| 888 | - if ($offset - $lastsentence > $maxlen) | |
| 889 | - { | |
| 890 | - $lastsentence = $offset - $maxlen; | |
| 891 | - } | |
| 892 | - | |
| 893 | - $text = substr($this->text, $lastsentence, $offset - $lastsentence); | |
| 894 | - | |
| 895 | - $wordoffset= strlen($text)-1; | |
| 896 | - $words = $maxwords; | |
| 897 | - while ($words > 0) | |
| 898 | - { | |
| 899 | - $text = substr($text, 0, $wordoffset); | |
| 900 | - $foundoffset = strrpos($text, ' '); | |
| 901 | - if ($foundoffset === false) | |
| 902 | - { | |
| 903 | - break; | |
| 904 | - } | |
| 905 | - $wordoffset = $foundoffset; | |
| 906 | - $words--; | |
| 907 | - } | |
| 908 | - $startOffset = $lastsentence + $wordoffset; | |
| 909 | - } | |
| 910 | - | |
| 911 | - | |
| 912 | - | |
| 913 | - $nextsentence = strpos($this->text, '.', $offset); | |
| 914 | - | |
| 915 | - $words = $maxwords; | |
| 916 | - $endOffset = $offset; | |
| 917 | - while ($words > 0) | |
| 918 | - { | |
| 919 | - $foundoffset = strpos($this->text, ' ', $endOffset+1); | |
| 920 | - if ($foundoffset === false) | |
| 921 | - { | |
| 922 | - break; | |
| 923 | - } | |
| 924 | - if ($endOffset > $offset + $maxlen) | |
| 925 | - { | |
| 926 | - break; | |
| 927 | - } | |
| 928 | - if ($endOffset > $nextsentence) | |
| 929 | - { | |
| 930 | - $endOffset = $nextsentence-1; | |
| 931 | - break; | |
| 932 | - } | |
| 933 | - $endOffset = $foundoffset; | |
| 934 | - | |
| 935 | - $words--; | |
| 936 | - } | |
| 937 | - | |
| 938 | - return array($startOffset, substr($this->text, $startOffset, $endOffset - $startOffset + 1)); | |
| 861 | + // not require! | |
| 862 | + return ''; | |
| 939 | 863 | } |
| 940 | - | |
| 941 | - | |
| 942 | - public function getResultText($result) | |
| 943 | - { | |
| 944 | - $this->text = substr($result->Text,0,40960); | |
| 945 | - $words = array(); | |
| 946 | - $sentences = array(); | |
| 947 | - | |
| 948 | - preg_match_all('("[^"]*")',$this->query, $matches,PREG_OFFSET_CAPTURE); | |
| 949 | - | |
| 950 | - foreach($matches[0] as $word) | |
| 951 | - { | |
| 952 | - list($word,$offset) = $word; | |
| 953 | - $word = substr($word,1,-1); | |
| 954 | - $wordlen = strlen($word); | |
| 955 | - $res = $this->extractText($word); | |
| 956 | - list($sentenceOffset,$sentence) = $res; | |
| 957 | - | |
| 958 | - if ($sentenceOffset === false) | |
| 959 | - { | |
| 960 | - continue; | |
| 961 | - } | |
| 962 | - | |
| 963 | - if (array_key_exists($sentenceOffset, $sentences)) | |
| 964 | - { | |
| 965 | - $sentences[$sentenceOffset]['score']++; | |
| 966 | - } | |
| 967 | - else | |
| 968 | - { | |
| 969 | - $sentences[$sentenceOffset] = array( | |
| 970 | - 'sentence'=>$sentence, | |
| 971 | - 'score'=>1 | |
| 972 | - ); | |
| 973 | - } | |
| 974 | - | |
| 975 | - $sentence = $sentences[$sentenceOffset]['sentence']; | |
| 976 | - | |
| 977 | - preg_match_all("@$word@i",$sentence, $swords,PREG_OFFSET_CAPTURE); | |
| 978 | - foreach($swords[0] as $wordx) | |
| 979 | - { | |
| 980 | - list($wordx,$offset) = $wordx; | |
| 981 | - | |
| 982 | - $sentence = substr($sentence,0, $offset) . '<b>' . substr($sentence, $offset, $wordlen) . '</b>' . substr($sentence, $offset + $wordlen); | |
| 983 | - } | |
| 984 | - | |
| 985 | - $sentences[$sentenceOffset]['sentence'] = $sentence; | |
| 986 | - | |
| 987 | - $words[$word] = array( | |
| 988 | - 'sentence'=>$sentenceOffset | |
| 989 | - ); | |
| 990 | - } | |
| 991 | - | |
| 992 | - ksort($sentences); | |
| 993 | - $result = ''; | |
| 994 | - | |
| 995 | - foreach($sentences as $o=>$i) | |
| 996 | - { | |
| 997 | - if (!empty($result)) $result .= ' ... '; | |
| 998 | - $result .= $i['sentence']; | |
| 999 | - } | |
| 1000 | - | |
| 1001 | - return $result; | |
| 1002 | - } | |
| 1003 | - | |
| 1004 | 864 | } |
| 1005 | 865 | |
| 1006 | 866 | class SQLQueryBuilder implements QueryBuilder |
| ... | ... | @@ -1226,7 +1086,10 @@ class SQLQueryBuilder implements QueryBuilder |
| 1226 | 1086 | |
| 1227 | 1087 | private function resolveMetadataOffset($expr) |
| 1228 | 1088 | { |
| 1229 | - assert($expr->left()->isMetadataField() ); | |
| 1089 | + if (!$expr->left()->isMetadataField()) | |
| 1090 | + { | |
| 1091 | + throw new Exception(_kt('Metadata field expected')); | |
| 1092 | + } | |
| 1230 | 1093 | |
| 1231 | 1094 | $offset=0; |
| 1232 | 1095 | foreach($this->metadata as $item) |
| ... | ... | @@ -2039,7 +1902,7 @@ class OpExpr extends Expr |
| 2039 | 1902 | $rank = $exprbuilder->getRanking($item); |
| 2040 | 1903 | if (!array_key_exists($document_id, $results) || $rank > $results[$document_id]->Rank) |
| 2041 | 1904 | { |
| 2042 | - $results[$document_id] = new MatchResult($document_id, $rank, $item['title'], $exprbuilder->getResultText($item)); | |
| 1905 | + $results[$document_id] = new QueryResultItem($document_id, $rank, $item['title'], $exprbuilder->getResultText($item)); | |
| 2043 | 1906 | } |
| 2044 | 1907 | } |
| 2045 | 1908 | |
| ... | ... | @@ -2069,7 +1932,7 @@ class OpExpr extends Expr |
| 2069 | 1932 | { |
| 2070 | 1933 | $item->Rank = $exprbuilder->getRanking($item); |
| 2071 | 1934 | $exprbuilder->setQuery($query); |
| 2072 | - $item->Text = $exprbuilder->getResultText($item); | |
| 1935 | + //$item->Text = $exprbuilder->getResultText($item); ?? wipe - done at indexer level | |
| 2073 | 1936 | } |
| 2074 | 1937 | |
| 2075 | 1938 | return $results; |
| ... | ... | @@ -2156,11 +2019,7 @@ class OpExpr extends Expr |
| 2156 | 2019 | $permResults = array(); |
| 2157 | 2020 | foreach($result as $idx=>$item) |
| 2158 | 2021 | { |
| 2159 | - $doc = Document::get($item->DocumentID); | |
| 2160 | - if (Permission::userHasDocumentReadPermission($doc)) | |
| 2161 | - { | |
| 2162 | - $permResults[$idx] = $item; | |
| 2163 | - } | |
| 2022 | + $permResults[$idx] = $item; | |
| 2164 | 2023 | } |
| 2165 | 2024 | |
| 2166 | 2025 | return $permResults; |
| ... | ... | @@ -2189,10 +2048,6 @@ class OpExpr extends Expr |
| 2189 | 2048 | $left->toViz($str, $phase); |
| 2190 | 2049 | $right->toViz($str, $phase); |
| 2191 | 2050 | } |
| 2192 | - | |
| 2193 | 2051 | } |
| 2194 | 2052 | |
| 2195 | - | |
| 2196 | - | |
| 2197 | - | |
| 2198 | 2053 | ?> |
| 2199 | 2054 | \ No newline at end of file | ... | ... |
templates/ktcore/search2/search_results.smarty
| ... | ... | @@ -4,7 +4,6 @@ |
| 4 | 4 | <div class="collapsible"> |
| 5 | 5 | <h4 onclick="toggleElementClass('expanded', this.parentNode)">{i18n}Search Expression{/i18n}</h4> |
| 6 | 6 | <div class="collapsiblebody"> |
| 7 | - | |
| 8 | 7 | <fieldset> |
| 9 | 8 | <legend>{i18n}Search Expression{/i18n}</legend> |
| 10 | 9 | {$txtQuery} |
| ... | ... | @@ -99,6 +98,8 @@ function onShowAll(showall) |
| 99 | 98 | |
| 100 | 99 | </script> |
| 101 | 100 | {/literal} |
| 101 | +</div> | |
| 102 | +</div> | |
| 102 | 103 | {if empty($iSavedID)} |
| 103 | 104 | <div id="saveSearch"> |
| 104 | 105 | <fieldset> |
| ... | ... | @@ -108,8 +109,8 @@ function onShowAll(showall) |
| 108 | 109 | </fieldset> |
| 109 | 110 | </div> |
| 110 | 111 | {/if} |
| 111 | -</div> | |
| 112 | -</div> | |
| 112 | + | |
| 113 | + | |
| 113 | 114 | <fieldset> |
| 114 | 115 | |
| 115 | 116 | <legend>{i18n}Search Results{/i18n}</legend> |
| ... | ... | @@ -132,8 +133,6 @@ function onShowAll(showall) |
| 132 | 133 | {assign var=cbid value=0} |
| 133 | 134 | |
| 134 | 135 | {foreach item=document from=$results} |
| 135 | - | |
| 136 | - {if $document->IsLive} | |
| 137 | 136 | <tr><td> |
| 138 | 137 | <input type="checkbox" name="selection_d[]" id="cb{$cbid}" value="{$document->DocumentID}"><nobr><a href="view.php?fDocumentId={$document->DocumentID}"><B>{$document->Title}</b></a> |
| 139 | 138 | {if $document->Title != $document->Filename} |
| ... | ... | @@ -150,36 +149,35 @@ function onShowAll(showall) |
| 150 | 149 | <font style=" color: orange ">{i18n}Version:{/i18n} {$document->Version}</font></td> |
| 151 | 150 | </tr> |
| 152 | 151 | <tr><td colspan=2>{$document->Text}</td></tr> |
| 153 | - <tr><td><font style=" color: green ">{$document->FullPath}/{$document->Title} - {$document->Filesize}</font></td> | |
| 152 | + <tr><td><font style=" color: green "><a href="{$rootUrl}/browse.php?fFolderId={$document->FolderId}">{$document->FullPath}</a>/{$document->Title} - {$document->Filesize}</font></td> | |
| 154 | 153 | <td align=right><nobr> |
| 155 | 154 | <font style="color: orange "> |
| 156 | - {i18n}Created By:{/i18n} Conrad Vermeulen | |
| 157 | - {i18n}on{/i18n} 2007-09-28 | |
| 155 | + {i18n}Created By:{/i18n} {$document->CreatedBy} | |
| 156 | + {i18n}on{/i18n} {$document->DateCreated} | |
| 158 | 157 | </font> |
| 159 | 158 | </td> |
| 160 | - {assign var=workflow value=$document->Workflow} | |
| 161 | - {if $workflow != '' || $this->Document->IsCheckedOut} | |
| 159 | + | |
| 160 | + | |
| 162 | 161 | <tr><td> |
| 163 | 162 | {if $workflow != ''} |
| 164 | - <font style="color: orange ">{i18n}Workflow:{/i18n} {$workflow}</font> | |
| 163 | + <font style="color: orange ">{i18n}Workflow:{/i18n} $document->Workflow}</font> | |
| 165 | 164 | {/if} |
| 166 | 165 | </td> |
| 167 | 166 | <td align=right><nobr> |
| 168 | 167 | <font style="color: brown "> |
| 169 | 168 | {if $document->CheckedOutUser != ''} |
| 170 | - {i18n}Checked out by:{/i18n} {$document->CheckedOutUser} | |
| 171 | - {i18n}on{/i18n} 2007-09-28 | |
| 169 | + <b>{i18n}Checked out by:{/i18n} {$document->CheckedOutUser} | |
| 170 | + {i18n}on{/i18n} {$document->DateCheckedOut}</b> | |
| 172 | 171 | {else} |
| 173 | - {i18n}Modified by:{/i18n} Conrad Vermeulen | |
| 174 | - {i18n}on{/i18n} 2007-09-28 | |
| 172 | + {i18n}Modified by:{/i18n} {$document->ModifiedBy} | |
| 173 | + {i18n}on{/i18n} {$document->DateModified} | |
| 175 | 174 | {/if} |
| 176 | 175 | </font> |
| 177 | 176 | </td> |
| 178 | - {/if} | |
| 177 | + | |
| 179 | 178 | <tr><td colspan=2><br></br></td></tr></tr> |
| 180 | 179 | |
| 181 | 180 | {assign var=cbid value=$cbid+1} |
| 182 | - {/if} | |
| 183 | 181 | {/foreach} |
| 184 | 182 | |
| 185 | 183 | <tr><td> |
| ... | ... | @@ -197,9 +195,11 @@ function onShowAll(showall) |
| 197 | 195 | <input type="hidden" name="fReturnAction" value="search2" /> |
| 198 | 196 | <input type="hidden" name="fReturnData" value="1" /> |
| 199 | 197 | |
| 198 | + <nobr> | |
| 200 | 199 | {foreach from=$bulkactions item=bulkaction} |
| 201 | 200 | <input type="submit" name="submit[{$bulkaction->getName()}]" value="{$bulkaction->getDisplayName()}" /> |
| 202 | 201 | {/foreach} |
| 202 | + </nobr> | |
| 203 | 203 | </table> |
| 204 | 204 | </form> |
| 205 | 205 | ... | ... |
thirdparty/Smarty/plugins/modifier.mb_truncate.php
| ... | ... | @@ -26,12 +26,14 @@ function smarty_modifier_mb_truncate($string, $length = 80, $etc = '...', |
| 26 | 26 | if ($length == 0) |
| 27 | 27 | return ''; |
| 28 | 28 | |
| 29 | - if (mb_strlen($string) > $length) { | |
| 30 | - $length -= mb_strlen($etc); | |
| 29 | + if (mb_strlen($string, 'UTF-8') > $length) { | |
| 30 | + mb_internal_encoding("UTF-8"); | |
| 31 | + | |
| 31 | 32 | if (!$break_words) |
| 32 | 33 | $string = preg_replace('/\s+?(\S+)?$/', '', mb_substr($string, 0, $length+1)); |
| 33 | 34 | |
| 34 | - return mb_substr($string, 0, $length).$etc; | |
| 35 | + return html_entity_decode(mb_substr($string, 0, $length, 'UTF-8').$etc, ENT_NOQUOTES, 'UTF-8'); | |
| 36 | + | |
| 35 | 37 | } else |
| 36 | 38 | return $string; |
| 37 | 39 | } | ... | ... |