diff --git a/download_notification.php b/download_notification.php new file mode 100644 index 0000000..9281b8f --- /dev/null +++ b/download_notification.php @@ -0,0 +1,51 @@ +getNotificationForm($head); +} + +exit; +?> \ No newline at end of file diff --git a/lib/foldermanagement/compressionArchiveUtil.inc.php b/lib/foldermanagement/compressionArchiveUtil.inc.php index fcec614..bddf3cf 100644 --- a/lib/foldermanagement/compressionArchiveUtil.inc.php +++ b/lib/foldermanagement/compressionArchiveUtil.inc.php @@ -311,6 +311,12 @@ class ZipFolder { KTUtil::download($sZipFile, $mimeType, $fileSize, $fileName); KTUtil::deleteDirectory($sTmpPath); + // remove notification file if it exists + DownloadQueue::removeNotificationFile(); + + // remove zip file database entry and any stragglers + DBUtil::whereDelete('download_queue', array('code' => $exportCode)); + return true; } @@ -398,6 +404,26 @@ class ZipFolder { return false; } + + /** + * Returns the zip file name with extension + * + * @return string + */ + public function getZipFileName() + { + return $this->sZipFileName . '.' . $this->extension; + } + + /** + * Returns the path to the zip file + * + * @return string + */ + public function getTmpPath() + { + return $this->sTmpPath; + } } /** @@ -410,6 +436,7 @@ class DownloadQueue private $bNotifications; private $errors; private $lockFile; + static private $notificationFile = 'download_queue_notification'; /** * Construct download queue object @@ -446,25 +473,25 @@ class DownloadQueue /** * Remove an item from the download queue + * Will not remove zip object types, these must be independently removed * * @param string $code Identification string for the download item * @return boolean | PEAR_Error */ public function removeItem($code) { - $where = array('code' => $code); - $res = DBUtil::whereDelete('download_queue', $where); + $res = DBUtil::runQuery('DELETE FROM download_queue WHERE code = "' . $code . '" AND object_type != "zip"'); return $res; } /** - * Get all download items in the queue + * Get all download items (other than zip object type) in the queue * * @return Queue array | PEAR_Error */ public function getQueue() { - $sql = 'SELECT * FROM download_queue d WHERE status = 0 ORDER BY date_added, code'; + $sql = 'SELECT * FROM download_queue d WHERE status = 0 AND object_type != "zip" ORDER BY date_added, code'; $rows = DBUtil::getResultArray($sql); if(PEAR::isError($rows)){ @@ -502,12 +529,15 @@ class DownloadQueue * @param string $error Optional. The error's generated during the archive * @return boolean */ - public function setItemStatus($code, $status = 1, $error = null) + public function setItemStatus($code, $status = 1, $error = null, $zip = false) { $fields = array(); $fields['status'] = $status; $fields['errors'] = !empty($error) ? json_encode($error) : null; $where = array('code' => $code); + if ($zip) { + $where['object_type'] = 'zip'; + } $res = DBUtil::whereUpdate('download_queue', $fields, $where); return $res; } @@ -617,6 +647,17 @@ class DownloadQueue $this->errors = null; $_SESSION['zipcompression'] = null; } + + if (count($queue) && !PEAR::isError($res) && !PEAR::isError($result)) { + // create the db entry + self::addItem($code, self::getFolderId($code), -1, 'zip'); + // update the db entry with the appropriate status and message + $this->setItemStatus($code, 2, serialize(array($zip->getTmpPath(), $zip->getZipFileName())), true); + // write a file which will be checked if the user has not been logged out and back in + // (in which case the required session value will not be set and this file acts as a trigger instead) + $config = KTConfig::getSingleton(); + @touch($config->get('cache/cacheDirectory') . '/' . self::$notificationFile); + } // Remove lock file @unlink($this->lockFile); @@ -757,7 +798,8 @@ class DownloadQueue * @param Folder array $aFolderList * @return unknown */ - function getLinkingEntities($aFolderList){ + function getLinkingEntities($aFolderList) + { $aSearchFolders = array(); if(!empty($aFolderList)){ foreach($aFolderList as $oFolderItem){ @@ -846,5 +888,142 @@ class DownloadQueue { return file_exists($this->lockFile); } + + /** + * The code below has all been added for bulk download notifications + * + * The functions were declared as static because they are related to but not entirely part of the download queue process + * and I wanted to use them without having to instantiate the class + */ + + /** + * Checks whether there are any bulk downloads which have passed the timeout limit + * Default limit is set to 48 hours but this can be changed in the calling code + * + * @param int $limit the number of hours after which a download should be considered timed out + */ + static public function timeout($limit = 48) + { + DBUtil::runQuery('DELETE FROM download_queue WHERE DATE_ADD(date_added, INTERVAL ' . $limit . ' HOUR) < NOW()'); + } + + /** + * Checks whether there is a bulk download available and waiting for the supplied user + * + * @param int $userID + */ + static public function userDownloadAvailable($userID) + { + $result = DBUtil::getOneResult('SELECT code, errors FROM download_queue WHERE user_id = ' . $userID + . ' AND object_type = "zip" AND status = 2'); + if (PEAR::isError($result)) { + return false; + } + + $code = $result['code']; + + $message = $result['errors']; + $message = json_decode($message, true); + $data = unserialize($message); + + // Create the archive session variables - I can't recall if this was needed, will have to find out by testing... + $_SESSION['zipcompression'][$code]['file'] = $data[0] . DIRECTORY_SEPARATOR . $data[1]; + $_SESSION['zipcompression'][$code]['dir'] = $data[0]; + + // Check that the archive file has been created + // NOTE If it does not exist, consider deleting the db entry and looping back to check again, only returning when no more results are found in the db? + // The reason for this is that it is sometimes possible to end up with multiple zips listed in the db as waiting for the same user, + // some of which may no longer exist as physical files, and since the request only checks for one, it may miss am existing download. + // NOTE the above issue may only occur due to the way I have been testing, often breaking the process before it is finished, + // but conceivably this could actually happen in the real world also. + $zip = new ZipFolder('', $code); + if($zip->checkArchiveExists($code)) { + return $code; + } + + return false; + } + + /** + * Delete the download, including the database entry + * + * @param string $code + */ + static public function deleteDownload($code) + { + // retrieve the data to allow deletion of the physical file + $result = DBUtil::getOneResult('SELECT errors FROM download_queue WHERE code = "' . $code . '"' + . ' AND object_type = "zip" AND status = 2'); + + if (PEAR::isError($result)) { + return $result; + } + + $message = $result['errors']; + $message = json_decode($message, true); + $data = unserialize($message); + + // remove the database entry + DBUtil::whereDelete('download_queue', array('code' => $code)); + + // remove the actual file/directory + KTUtil::deleteDirectory($data[0]); + + // remove the notification file if present + self::removeNotificationFile(); + } + + /** + * Removes the notification file, if it exists, in a safe manner + * + * @param string $code + */ + static public function removeNotificationFile() { + $config = KTConfig::getSingleton(); + $file = DownloadQueue::getNotificationFileName(); + if (!PEAR::isError($file)) { + $notificationFile = $config->get('cache/cacheDirectory') . '/' . $file; + } + + // ensure the file is actually a file and not a directory + if (is_file($notificationFile)) { + @unlink($notificationFile); + } + } + + /** + * Fetches a link to the download + * + * @param string $code + */ + static public function getDownloadLink($code) + { + return KTUtil::kt_url() . '/action.php?kt_path_info=ktcore.actions.bulk.export&action=downloadZipFile&fFolderId=' . self::getFolderId($code) . '&exportcode=' . $code; + } + + /** + * Fetches the folder id associated with the supplied code + * Based on testing just before file corruption this may not actually be necessary, but we'll do it anyway + * + * @param string $code + */ + static public function getFolderId($code) + { + $result = DBUtil::getOneResult('SELECT folder_id FROM download_queue WHERE code = "' . $code . '"'); + + if (PEAR::isError($result)) { + return -1; + } + + return $result['folder_id']; + } + + static public function getNotificationFileName() { + if (!empty(self::$notificationFile)) { + return self::$notificationFile; + } + + return new PEAR_Error('Unable to get file name'); + } } ?> diff --git a/lib/foldermanagement/downloadNotification.inc.php b/lib/foldermanagement/downloadNotification.inc.php new file mode 100644 index 0000000..c614a4b --- /dev/null +++ b/lib/foldermanagement/downloadNotification.inc.php @@ -0,0 +1,112 @@ +code = $code; + } + + /** + * Returns the form displaying the download link (and option to cancel the download?) + * + * @author KnowledgeTree Team + * @access public + * @param string $head The heading for the form + * @param string $request_type Determines the actions for the buttons + * @return html + */ + public function getNotificationForm($head) + { + global $default; + + // check for the download link + + $link = DownloadQueue::getDownloadLink($this->code); + $text = 'Download Now'; + + $cancelAction = 'deleteDownload()'; + $closeAction = 'deferDownload()'; + + $oTemplating =& KTTemplating::getSingleton(); + $oTemplate = $oTemplating->loadTemplate('kt3/notifications/notification.BulkDownload'); + $aTemplateData = array( + 'head' => $head, + 'downloadLink' => $link, + 'downloadText' => $text, + 'exportCode' => $this->code, + 'cancelAction' => $cancelAction, + 'closeAction' => $closeAction + ); + + return $oTemplate->render($aTemplateData); + } + + /** + * Returns the error from the attempted signature + * + * @author KnowledgeTree Team + * @access public + * @return string + */ + public function getError() + { + return '
'.$this->error.'
'; + } + + /** + * Displays a button for closing the panel + * + * @author KnowledgeTree Team + * @access public + * @param string $request_type Determines the action taken on close - close = redirect action | null = close panel action + * @param string $request Optional. Used within the close action. + * @return string + */ + public function getCloseButton($request_type, $request = null) + { + switch ($request_type){ + case 'close': + $cancelAction = "window.location.href = '{$request}'"; + break; + + default: + $cancelAction = "panel_close()"; + } + + return '
+ '._kt('Close').' +
'; + } +} + +?> \ No newline at end of file diff --git a/lib/templating/kt3template.inc.php b/lib/templating/kt3template.inc.php index d8978cb..49f57f6 100644 --- a/lib/templating/kt3template.inc.php +++ b/lib/templating/kt3template.inc.php @@ -353,8 +353,9 @@ class KTPage { function setSecondaryTitle($sSecondary) { $this->secondary_title = $sSecondary; } /* final render call. */ - function render() { - global $default; + function render() + { + global $default; $oConfig = KTConfig::getSingleton(); if (empty($this->contents)) { @@ -366,70 +367,68 @@ class KTPage { $this->contents = ""; } - if (!is_string($this->contents)) { - $this->contents = $this->contents->render(); - } - - // if we have no portlets, make the ui a tad nicer. - if (empty($this->portlets)) { - $this->show_portlets = false; - } - - if (empty($this->title)) { - if (!empty($this->breadcrumbDetails)) { - $this->title = $this->breadcrumbDetails; - } else if (!empty($this->breadcrumbs)) { - $this->title = array_slice($this->breadcrumbs, -1); - $this->title = $this->title[0]['label']; - } else if (!empty($this->breadcrumbSection)) { - $this->title = $this->breadcrumbSection['label']; - } else { - $this->title = $this->componentLabel; - } - } - - $this->userMenu = array(); - $sBaseUrl = KTUtil::kt_url(); + if (!is_string($this->contents)) { + $this->contents = $this->contents->render(); + } - if (!(PEAR::isError($this->user) || is_null($this->user) || $this->user->isAnonymous())) { - if ($oConfig->get("user_prefs/restrictPreferences", false) && !Permission::userIsSystemAdministrator($this->user->getId())) { - $this->userMenu['logout'] = array('label' => _kt('Logout'), 'url' => $sBaseUrl.'/presentation/logout.php'); - } else { + // if we have no portlets, make the ui a tad nicer. + if (empty($this->portlets)) { + $this->show_portlets = false; + } - if($default->enableESignatures){ - $sUrl = KTPluginUtil::getPluginPath('electronic.signatures.plugin', true); - $heading = _kt('You are attempting to modify Preferences'); - $this->userMenu['preferences']['url'] = '#'; - $this->userMenu['preferences']['onclick'] = "javascript: showSignatureForm('{$sUrl}', '{$heading}', 'dms.administration.accessing_preferences', 'system', '{$sBaseUrl}/preferences.php', 'redirect');"; - }else{ - $this->userMenu['preferences']['url'] = $sBaseUrl.'/preferences.php'; + if (empty($this->title)) { + if (!empty($this->breadcrumbDetails)) { + $this->title = $this->breadcrumbDetails; + } else if (!empty($this->breadcrumbs)) { + $this->title = array_slice($this->breadcrumbs, -1); + $this->title = $this->title[0]['label']; + } else if (!empty($this->breadcrumbSection)) { + $this->title = $this->breadcrumbSection['label']; + } else { + $this->title = $this->componentLabel; } + } -// $this->userMenu['preferences'] = array('label' => _kt('Preferences'), 'url' => $sBaseUrl.'/preferences.php'); - $this->userMenu['preferences']['label'] = _kt('Preferences'); - $this->userMenu['aboutkt'] = array('label' => _kt('About'), 'url' => $sBaseUrl.'/about.php'); - $this->userMenu['logout'] = array('label' => _kt('Logout'), 'url' => $sBaseUrl.'/presentation/logout.php'); - } - } else { - $this->userMenu['login'] = array('label' => _kt('Login'), 'url' => $sBaseUrl.'/login.php'); - } - - // FIXME we need a more complete solution to navigation restriction - if (!is_null($this->menu['administration']) && !is_null($this->user)) { - if (!Permission::userIsSystemAdministrator($this->user->getId())) { - unset($this->menu['administration']); - } - } + $this->userMenu = array(); + $sBaseUrl = KTUtil::kt_url(); + + if (!(PEAR::isError($this->user) || is_null($this->user) || $this->user->isAnonymous())) { + if ($oConfig->get("user_prefs/restrictPreferences", false) && !Permission::userIsSystemAdministrator($this->user->getId())) { + $this->userMenu['logout'] = array('label' => _kt('Logout'), 'url' => $sBaseUrl.'/presentation/logout.php'); + } else { + if($default->enableESignatures) { + $sUrl = KTPluginUtil::getPluginPath('electronic.signatures.plugin', true); + $heading = _kt('You are attempting to modify Preferences'); + $this->userMenu['preferences']['url'] = '#'; + $this->userMenu['preferences']['onclick'] = "javascript: showSignatureForm('{$sUrl}', '{$heading}', 'dms.administration.accessing_preferences', 'system', '{$sBaseUrl}/preferences.php', 'redirect');"; + } else { + $this->userMenu['preferences']['url'] = $sBaseUrl.'/preferences.php'; + } + + // $this->userMenu['preferences'] = array('label' => _kt('Preferences'), 'url' => $sBaseUrl.'/preferences.php'); + $this->userMenu['preferences']['label'] = _kt('Preferences'); + $this->userMenu['aboutkt'] = array('label' => _kt('About'), 'url' => $sBaseUrl.'/about.php'); + $this->userMenu['logout'] = array('label' => _kt('Logout'), 'url' => $sBaseUrl.'/presentation/logout.php'); + } + } else { + $this->userMenu['login'] = array('label' => _kt('Login'), 'url' => $sBaseUrl.'/login.php'); + } - $sContentType = 'Content-type: ' . $this->contentType; - if(!empty($this->charset)) { - $sContentType .= '; charset=' . $this->charset; - }; + // FIXME we need a more complete solution to navigation restriction + if (!is_null($this->menu['administration']) && !is_null($this->user)) { + if (!Permission::userIsSystemAdministrator($this->user->getId())) { + unset($this->menu['administration']); + } + } + $sContentType = 'Content-type: ' . $this->contentType; + if(!empty($this->charset)) { + $sContentType .= '; charset=' . $this->charset; + }; - header($sContentType); + header($sContentType); - $savedSearches = SearchHelper::getSavedSearches($_SESSION['userID']); + $savedSearches = SearchHelper::getSavedSearches($_SESSION['userID']); $oTemplating =& KTTemplating::getSingleton(); $oTemplate = $oTemplating->loadTemplate($this->template); @@ -442,13 +441,24 @@ class KTPage { if ($oConfig->get("ui/automaticRefresh", false)) { $aTemplateData['refreshTimeout'] = (int)$oConfig->get("session/sessionTimeout") + 3; } + + // Trigger for pending downloads + $aTemplateData['downloadNotification'] = null; + require_once(KT_LIB_DIR . '/triggers/triggerregistry.inc.php'); + $oKTTriggerRegistry = KTTriggerRegistry::getSingleton(); + $aTriggers = $oKTTriggerRegistry->getTriggers('ktcore', 'pageLoad'); + foreach ($aTriggers as $aTrigger) { + $sTrigger = $aTrigger[0]; + $oTrigger = new $sTrigger; + $aTemplateData['downloadNotification'] = $oTrigger->invoke(); + } // unlike the rest of KT, we use echo here. echo $oTemplate->render($aTemplateData); } - /** heler functions */ + /** helper functions */ // returns an array ("url", "label") function _actionhelper($aActionTuple) { $aTuple = Array("label" => $aActionTuple["name"]); diff --git a/login.php b/login.php index f26c827..24cedf1 100644 --- a/login.php +++ b/login.php @@ -116,6 +116,9 @@ class LoginPageDispatcher extends KTDispatcher { if (PEAR::isError($sessionID)) { return $sessionID; } + + // add a flag to check for bulk downloads after login is succesful; this will be cleared in the code which checks + $_SESSION['checkBulkDownload'] = true; $redirect = strip_tags(KTUtil::arrayGet($_REQUEST, 'redirect')); diff --git a/plugins/ktcore/KTBulkActions.php b/plugins/ktcore/KTBulkActions.php index b54b664..a022d00 100644 --- a/plugins/ktcore/KTBulkActions.php +++ b/plugins/ktcore/KTBulkActions.php @@ -1197,7 +1197,7 @@ class KTBrowseBulkExportAction extends KTBulkAction { * */ function perform_action($oEntity) { -// TODO find a way to do bulk email + // TODO find a way to do bulk email $exportCode = $_SESSION['exportcode']; $this->oZip = ZipFolder::get($exportCode); diff --git a/plugins/ktcore/KTCorePlugin.php b/plugins/ktcore/KTCorePlugin.php index 7b3ca7d..fdd877c 100755 --- a/plugins/ktcore/KTCorePlugin.php +++ b/plugins/ktcore/KTCorePlugin.php @@ -169,11 +169,13 @@ class KTCorePlugin extends KTPlugin { $this->registerTrigger('add', 'postValidate', 'SavedSearchSubscriptionTrigger', 'ktcore.search2.savedsearch.subscription.add', KT_DIR . '/plugins/search2/Search2Triggers.php'); $this->registerTrigger('discussion', 'postValidate', 'SavedSearchSubscriptionTrigger', 'ktcore.search2.savedsearch.subscription.discussion', KT_DIR . '/plugins/search2/Search2Triggers.php'); - //Tag Cloud Triggers + // Tag Cloud Triggers $this->registerTrigger('add', 'postValidate', 'KTAddDocumentTrigger', 'ktcore.triggers.tagcloud.add', KT_DIR.'/plugins/tagcloud/TagCloudTriggers.php'); $this->registerTrigger('edit', 'postValidate', 'KTEditDocumentTrigger', 'ktcore.triggers.tagcloud.edit', KT_DIR.'/plugins/tagcloud/TagCloudTriggers.php'); - + // Bulk Download Trigger + $this->registerTrigger('ktcore', 'pageLoad', 'BulkDownloadTrigger', 'ktcore.triggers.pageload', 'KTDownloadTriggers.inc.php'); + // widgets $this->registerWidget('KTCoreInfoWidget', 'ktcore.widgets.info', 'KTWidgets.php'); $this->registerWidget('KTCoreHiddenWidget', 'ktcore.widgets.hidden', 'KTWidgets.php'); @@ -198,6 +200,7 @@ class KTCorePlugin extends KTPlugin { $this->registerWidget('KTCoreLayerWidget', 'ktcore.widgets.layer', 'KTWidgets.php'); $this->registerWidget('KTCoreConditionalSelectionWidget', 'ktcore.widgets.conditionalselection', 'KTWidgets.php'); $this->registerWidget('KTCoreImageWidget', 'ktcore.widgets.image', 'KTWidgets.php'); + $this->registerWidget('KTCoreImageSelectWidget', 'ktcore.widgets.imageselect', 'KTWidgets.php'); $this->registerWidget('KTCoreImageCropWidget', 'ktcore.widgets.imagecrop', 'KTWidgets.php'); $this->registerPage('collection', 'KTCoreCollectionPage', 'KTWidgets.php'); diff --git a/plugins/ktcore/KTDownloadTriggers.inc.php b/plugins/ktcore/KTDownloadTriggers.inc.php new file mode 100644 index 0000000..565589e --- /dev/null +++ b/plugins/ktcore/KTDownloadTriggers.inc.php @@ -0,0 +1,91 @@ +. + * + * You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, + * California 94120-7775, or email info@knowledgetree.com. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU General Public License version 3. + * + * In accordance with Section 7(b) of the GNU General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "Powered by + * KnowledgeTree" logo and retain the original copyright notice. If the display of the + * logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices + * must display the words "Powered by KnowledgeTree" and retain the original + * copyright notice. + * Contributor( s): ______________________________________ + * + */ + +class BulkDownloadTrigger { + + var $sNamespace = 'ktcore.triggers.download'; + var $sFriendlyName; + var $sDescription; + + function __construct() + { + $this->sFriendlyName = _kt('Bulk Download Notification'); + $this->sDescription = _kt('Notifies the newly logged in user of any waiting bulk downloads.'); + } + + public function invoke() + { + // TODO get this working with triggers instead of this? + // check for timed out downloads - now we may be looking at enough code to justify a trigger instead of all being here...? + DownloadQueue::timeout(); + + // determine whether there is a waiting bulk download + global $main; + // first check whether there is in fact a download waiting for this user + $config = KTConfig::getSingleton(); + $file = DownloadQueue::getNotificationFileName(); + if (!PEAR::isError($file)) { + $notificationFile = $config->get('cache/cacheDirectory') . '/' . $file; + } + if ((isset($_SESSION['checkBulkDownload']) && ($_SESSION['checkBulkDownload'])) || (file_exists($notificationFile))) { + unset($_SESSION['checkBulkDownload']); + DownloadQueue::removeNotificationFile(); + + require_once(KT_LIB_DIR . DIRECTORY_SEPARATOR . 'foldermanagement' . DIRECTORY_SEPARATOR . 'compressionArchiveUtil.inc.php'); + + $userID = $_SESSION['userID']; + $code = DownloadQueue::userDownloadAvailable($userID); + if ($code) { + $sBaseUrl = KTUtil::kt_url(); + $sUrl = $sBaseUrl . '/'; + $heading = _kt('You have a download waiting'); + $main->requireJSResource('resources/js/download_notification.js'); + $notification = "showDownloadNotification('{$sUrl}', '{$heading}', 'dms.users.bulk_download_notification', " + . "'{$code}', 'close');"; + } + + return $notification; + } + + return null; + } + +} + +?> diff --git a/plugins/ktcore/KTWidgets.php b/plugins/ktcore/KTWidgets.php index 8cbf53d..d074654 100755 --- a/plugins/ktcore/KTWidgets.php +++ b/plugins/ktcore/KTWidgets.php @@ -1140,6 +1140,56 @@ class KTCoreImageWidget extends KTWidget { return $oTemplate->render($aTemplateData); } +} + +class KTCoreImageSelectWidget extends KTWidget { + var $sNamespace = 'ktcore.widgets.imageselect'; + var $sTemplate = 'ktcore/forms/widgets/imageselect'; + + function configure($aOptions) { + $res = parent::configure($aOptions); + if (PEAR::isError($res)) { + return $res; + } + + } + + function render() { + $oTemplating =& KTTemplating::getSingleton(); + $oTemplate = $oTemplating->loadTemplate('ktcore/forms/widgets/base'); + + $this->aJavascript[] = 'thirdpartyjs/jquery/jquery-1.3.2.js'; + + if (!empty($this->aJavascript)) { + // grab our inner page. + $oPage =& $GLOBALS['main']; + $oPage->requireJSResources($this->aJavascript); + } + + $this->aCSS[] = 'resources/css/kt_imageselect.css'; + + if (!empty($this->aCSS)) { + // grab our inner page. + $oPage =& $GLOBALS['main']; + $oPage->requireCSSResources($this->aCSS); + } + + $widget_content = $this->getWidget(); + + $aTemplateData = array( + "context" => $this, + "label" => $this->sLabel, + "description" => $this->sDescription, + "name" => $this->sName, + "has_value" => ($this->value !== null), + "value" => $this->value, + "has_errors" => $bHasErrors, + "errors" => $this->aErrors, + "options" => $this->aOptions, + "widget" => $widget_content, + ); + return $oTemplate->render($aTemplateData); + } } diff --git a/plugins/ktcore/admin/manageBranding.php b/plugins/ktcore/admin/manageBranding.php index 107c71f..825e229 100755 --- a/plugins/ktcore/admin/manageBranding.php +++ b/plugins/ktcore/admin/manageBranding.php @@ -84,7 +84,7 @@ class ManageBrandDispatcher extends KTAdminDispatcher { 'encoding' => 'multipart/form-data', 'context' => &$this, 'extraargs' => $this->meldPersistQuery("","",true), - 'description' => _kt('The logo upload facility allows you to upload a logo to brand your knowledgetree site.') + 'description' => _kt('You can upload a logo to brand your KnowledgeTree site.') )); $oWF =& KTWidgetFactory::getSingleton(); @@ -99,7 +99,7 @@ class ManageBrandDispatcher extends KTAdminDispatcher { 'name' => 'file', 'id' => 'file', 'value' => '', - 'description' => _kt('The logo should be 313px by 50px in dimension. If you don\'t have a 313x50 logo you should choose to either "crop" or "scale" it. If you are certain that your logo has the correct dimentions you can safely skip by the selecting "Don\'t do anything"'), + 'description' => _kt("The logo's dimensions should be 313px width by 50px height. If your logo doesn't fit these dimensions, you can choose to crop or scale it."), )); $aVocab['crop'] = 'Crop - Cut out a selection'; @@ -142,6 +142,61 @@ class ManageBrandDispatcher extends KTAdminDispatcher { /** + * Returns the scale logo form + * + * This form will display a preview of all the possible sclaled combinations. + * This includes: + * + * Stretched, Top Left Cropped, Proportional Stretch, Proportional Top Left Cropped + * + * @return KTForm + * + */ + + function getScaleLogoForm($logoItems = array()) { + $this->oPage->setBreadcrumbDetails(_kt("Scale Logo")); + + $oForm = new KTForm; + $oForm->setOptions(array( + 'identifier' => 'ktcore.folder.branding', + 'label' => _kt('Choose Logo'), + 'submit_label' => _kt('Select'), + 'action' => 'selectLogo', + 'fail_action' => 'main', + 'encoding' => 'multipart/form-data', + 'context' => &$this, + 'extraargs' => $this->meldPersistQuery("","",true), + 'description' => _kt('Choose a logo by clicking on one of the images') + )); + + $oWF =& KTWidgetFactory::getSingleton(); + + $widgets = array(); + $validators = array(); + + $logoFileName = 'var'.DIRECTORY_SEPARATOR.'branding'.DIRECTORY_SEPARATOR.'logo'.DIRECTORY_SEPARATOR.$logoFileName; + + // Adding the image select widget (User will select the best image) + $widgets[] = $oWF->get('ktcore.widgets.imageselect', array( + 'label' => _kt('Logo Preview'), + 'name' => $logoFileName, + 'value' => $logoItems, + )); + + // Adding the Hidden FileName Input String + $widgets[] = $oWF->get('ktcore.widgets.hidden', array( + 'name' => 'kt_imageselect', + 'value' => $logoItems[0], + )); + + $oForm->setWidgets($widgets); + $oForm->setValidators($validators); + + return $oForm; + } + + + /** * Returns the crop logo form * * This form will assist the user in selecting an area of the image to use as the logo @@ -311,7 +366,7 @@ class ManageBrandDispatcher extends KTAdminDispatcher { //Changing to logo.jpg (Need to preserve extention as GD requires the exact image type to work) $ext = end(explode('.', $logoFileName)); - $logoFileName = 'logo_tmp.'.$ext; + $logoFileName = 'logo_tmp_'.md5(Date('ymd-hms')).'.'.$ext; //Fighting the browser cache here $logoFile = $logoDir.DIRECTORY_SEPARATOR.$logoFileName; // deleting old tmp file @@ -329,6 +384,8 @@ class ManageBrandDispatcher extends KTAdminDispatcher { $resizeMethod = $_REQUEST['data']['resize_method']; + $relDir = 'var'.DIRECTORY_SEPARATOR.'branding'.DIRECTORY_SEPARATOR.'logo'.DIRECTORY_SEPARATOR; + switch ($resizeMethod) { case 'crop': $cropLogoForm = $this->getCropLogoForm($logoFileName); @@ -336,9 +393,24 @@ class ManageBrandDispatcher extends KTAdminDispatcher { case 'scale': $type = $_FILES['_kt_attempt_unique_file']['type']; - $res = $this->scaleImage($logoFile, $logoFile, $this->maxLogoWidth, $this->maxLogoHeight, $type); - $form = $this->getApplyLogoForm($logoFileName); + $logoFileNameStretched = 'logo_tmp_stretched_'.md5(Date('ymd-hms')).'.'.$ext; //Fighting the browser cache here + $logoFileStretched = $logoDir.DIRECTORY_SEPARATOR.$logoFileNameStretched; + + $logoFileNameCropped = 'logo_tmp_cropped_'.md5(Date('ymd-hms')).'.'.$ext; //Fighting the browser cache here + $logoFileCropped = $logoDir.DIRECTORY_SEPARATOR.$logoFileNameCropped; + + //Creating stretched image + $res = $this->scaleImage($logoFile, $logoFileStretched, $this->maxLogoWidth, $this->maxLogoHeight, $type, false, false); + + //Creating top-left cropped image + $res = $this->scaleImage($logoFile, $logoFileCropped, $this->maxLogoWidth, $this->maxLogoHeight, $type, false, true); + $res = $this->cropImage($logoFileCropped, $logoFileCropped, 0, 0, $this->maxLogoWidth, $this->maxLogoHeight, $type); + + $logoItem[] = $relDir.$logoFileNameStretched; + $logoItem[] = $relDir.$logoFileNameCropped; + + $form = $this->getScaleLogoForm($logoItem); return $form->render(); default: @@ -354,7 +426,7 @@ class ManageBrandDispatcher extends KTAdminDispatcher { * - Supported images are jpeg, png and gif * */ - public function scaleImage( $origFile, $destFile, $width, $height, $type = 'image/jpeg', $scaleUp = true) { + public function scaleImage( $origFile, $destFile, $width, $height, $type = 'image/jpeg', $scaleUp = false, $keepProportion = true) { global $default; //Requires the GD library if not exit gracefully @@ -399,7 +471,11 @@ class ManageBrandDispatcher extends KTAdminDispatcher { $image_x = $width; $image_y = $height; - //$image_y = round(($orig_y * $image_x) / $orig_x); //Preserve proportion + + //Constraining proportion + if ($keepProportion) { + $image_y = round(($orig_y * $image_x) / $orig_x); //Preserve proportion + } /* * create the new image, and scale the original into it. @@ -426,7 +502,6 @@ class ManageBrandDispatcher extends KTAdminDispatcher { return false; } - } else { //Handle Error $default->log->error("Couldn't obtain a valid GD resource"); @@ -446,9 +521,13 @@ class ManageBrandDispatcher extends KTAdminDispatcher { function do_crop(){ global $default; - $logoFileName = $_REQUEST['data']['logo_file_name']; + $logoFileName = $_REQUEST['data']['logo_file_name']; $logoFile = 'var'.DIRECTORY_SEPARATOR.'branding'.DIRECTORY_SEPARATOR.'logo'.DIRECTORY_SEPARATOR.$logoFileName; - + + $ext = end(explode('.', $logoFileName)); + $destFileName = 'logo_tmp_'.md5(Date('ymd-hms')).'.'.$ext; + $destFile = 'var'.DIRECTORY_SEPARATOR.'branding'.DIRECTORY_SEPARATOR.'logo'.DIRECTORY_SEPARATOR.$destFileName; + $x1 = $_REQUEST['data']['crop_x1']; $y1 = $_REQUEST['data']['crop_y1']; @@ -458,7 +537,7 @@ class ManageBrandDispatcher extends KTAdminDispatcher { $type = $this->getMime($logoFileName); //GD Crop - $res = $this->cropImage($logoFile, $logoFile, $x1, $y1, $x2, $y2, $type); + $res = $this->cropImage($logoFile, $destFile, $x1, $y1, $x2, $y2, $type); //If dimensions don't conform then will scale it further $width = $x2 - $x1; @@ -466,7 +545,7 @@ class ManageBrandDispatcher extends KTAdminDispatcher { if (($width > $this->maxLogoWidth) || ($height > $this->maxLogoHeight)) { $default->log->info('SCALING IMAGE AFTER CROP'); - $res = $this->scaleImage($logoFile, $logoFile, $this->maxLogoWidth, $this->maxLogoHeight, $type); + $res = $this->scaleImage($destFile, $destFile, $this->maxLogoWidth, $this->maxLogoHeight, $type, false, false); } // ImageMagick Crop @@ -485,7 +564,7 @@ class ManageBrandDispatcher extends KTAdminDispatcher { $result = KTUtil::pexec($cmd); */ - $applyLogoForm = $this->getApplyLogoForm($logoFileName); + $applyLogoForm = $this->getApplyLogoForm($destFileName); return $applyLogoForm->render(); } @@ -600,6 +679,21 @@ class ManageBrandDispatcher extends KTAdminDispatcher { } + /* + * Action responsible for selecting the logo after it has been scaled. + * + */ + function do_selectLogo(){ + global $default; + + $tmpLogoFileName = end(explode(DIRECTORY_SEPARATOR, $_REQUEST['kt_imageselect'])); + + $form = $this->getApplyLogoForm($tmpLogoFileName); + return $form->render(); + + } + + /* * Action responsible for applying the logo @@ -628,10 +722,12 @@ class ManageBrandDispatcher extends KTAdminDispatcher { $brandDir = $default->varDirectory.DIRECTORY_SEPARATOR.'branding'.DIRECTORY_SEPARATOR.'logo'.DIRECTORY_SEPARATOR; $handle = opendir($brandDir); while (false !== ($file = readdir($handle))) { - if (!is_dir($file) && $file != 'logo_tmp.jpg' && $file != $logoFileName) { + if (!is_dir($file) && $file != $tmpLogoFileName && $file != $logoFileName) { if (!@unlink($brandDir.$file)) { $default->log->error("Couldn't delete '".$brandDir.$file."'"); - } + } else { + $default->log->error("Cleaning Brand Logo Dir: Deleted '".$brandDir.$file."'"); + } } } } @@ -665,128 +761,6 @@ class ManageBrandDispatcher extends KTAdminDispatcher { $this->successRedirectTo('', _kt("Logo succesfully applied.")); } - - /* - - Old Manage Views Code! - Crap, must delete - - */ - function do_editView() { - $oTemplating =& KTTemplating::getSingleton(); - $oTemplate = $oTemplating->loadTemplate('ktcore/misc/columns/edit_view'); - - $oColumnRegistry =& KTColumnRegistry::getSingleton(); - $aColumns = $oColumnRegistry->getColumnsForView($_REQUEST['viewNS']); - //var_dump($aColumns); exit(0); - $aAllColumns = $oColumnRegistry->getColumns(); - - $view_name = $oColumnRegistry->getViewName(($_REQUEST['viewNS'])); - $this->oPage->setTitle($view_name); - $this->oPage->setBreadcrumbDetails($view_name); - - $aOptions = array(); - $vocab = array(); - foreach ($aAllColumns as $aInfo) { - $vocab[$aInfo['namespace']] = $aInfo['name']; - } - $aOptions['vocab'] = $vocab; - $add_field = new KTLookupWidget(_kt("Columns"), _kt("Select a column to add to the view. Please note that while you can add multiple copies of a column, they will all behave as a single column"), 'column_ns', null, $this->oPage, true, null, $aErrors = null, $aOptions); - - $aTemplateData = array( - 'context' => $this, - 'current_columns' => $aColumns, - 'all_columns' => $aAllColumns, - 'view' => $_REQUEST['viewNS'], - 'add_field' => $add_field, - ); - return $oTemplate->render($aTemplateData); - } - - function do_deleteEntry() { - $entry_id = KTUtil::arrayGet($_REQUEST, 'entry_id'); - $view = KTUtil::arrayGet($_REQUEST, 'viewNS'); - - // none of these conditions can be reached "normally". - - $oEntry = KTColumnEntry::get($entry_id); - if (PEAR::isError($oEntry)) { - $this->errorRedirectToMain(_kt("Unable to locate the entry")); - } - - if ($oEntry->getRequired()) { - $this->errorRedirectToMain(_kt("That column is required")); - } - - if ($oEntry->getViewNamespace() != $view) { - $this->errorRedirectToMain(_kt("That column is not for the specified view")); - } - - $res = $oEntry->delete(); - - if (PEAR::isError($res)) { - $this->errorRedirectToMain(sprintf(_kt("Failed to remove that column: %s"), $res->getMessage())); - } - - $this->successRedirectTo("editView", _kt("Deleted Entry"), sprintf("viewNS=%s", $view)); - } - - function do_addEntry() { - $column_ns = KTUtil::arrayGet($_REQUEST, 'column_ns'); - $view = KTUtil::arrayGet($_REQUEST, 'viewNS'); - - $this->startTransaction(); - - $position = KTColumnEntry::getNextEntryPosition($view); - $oEntry = KTColumnEntry::createFromArray(array( - 'ColumnNamespace' => $column_ns, - 'ViewNamespace' => $view, - 'Position' => $position, // start it at the bottom - 'config' => array(), // stub, for now. - 'Required' => 0 - )); - - $this->successRedirectTo("editView", _kt("Added Entry"), sprintf("viewNS=%s", $view)); - } - - function do_orderUp(){ - $entryId = $_REQUEST['entry_id']; - $view = $_REQUEST['viewNS']; - - $oEntry = KTColumnEntry::get($entryId); - if (PEAR::isError($oEntry)) { - $this->errorRedirectTo('editView', _kt('Unable to locate the column entry'), "viewNS={$view}"); - exit(); - } - - $res = $oEntry->movePosition($view, $entryId, 'up'); - if (PEAR::isError($res)) { - $this->errorRedirectTo('editView', $res->getMessage(), "viewNS={$view}"); - exit(); - } - - $this->redirectTo('editView', "viewNS={$view}"); - } - - function do_orderDown(){ - $entryId = $_REQUEST['entry_id']; - $view = $_REQUEST['viewNS']; - - $oEntry = KTColumnEntry::get($entryId); - if (PEAR::isError($oEntry)) { - $this->errorRedirectTo('editView', _kt('Unable to locate the column entry'), "viewNS={$view}"); - exit(); - } - - $res = $oEntry->movePosition($view, $entryId, 'down'); - if (PEAR::isError($res)) { - $this->errorRedirectTo('editView', $res->getMessage(), "viewNS={$view}"); - exit(); - } - - $this->redirectTo("editView", "viewNS={$view}"); - } - } ?> diff --git a/resources/css/kt_imageselect.css b/resources/css/kt_imageselect.css new file mode 100644 index 0000000..dafaf44 --- /dev/null +++ b/resources/css/kt_imageselect.css @@ -0,0 +1,11 @@ +#kt_image_select_container img { + cursor: pointer; + text-decoration: none; + border: 10px solid white; +} + +#kt_image_select_container img:hover { + text-decoration: none; + border: 10px solid white; +} + diff --git a/resources/js/download_notification.js b/resources/js/download_notification.js new file mode 100644 index 0000000..ef02a84 --- /dev/null +++ b/resources/js/download_notification.js @@ -0,0 +1,128 @@ +var win; +var head; +var sUrl; +var type; +var request; +var request_type; +var request_details; + +/* +* Create the download notification dialog +*/ +var showDownloadNotification = function(sUrl, head, action, code, request_type, details){ + createNotification(); + + this.sUrl = sUrl + 'download_notification.php'; + + if(details === undefined) details = ''; + if(request_type === undefined) request_type = 'submit'; + if(type === undefined) type = 'system'; + + this.head = head; + this.code = code; + this.request = request; + this.request_type = request_type; + this.request_details = new Array(); + this.request_details[0] = action; + this.request_details[1] = details; + + // create the window + this.win = new Ext.Window({ + applyTo : 'download_notification', + layout : 'fit', + width : 370, + height : 150, + resizable : false, + closable : false, + closeAction :'destroy', + y : 150, + shadow: false, + modal: true + }); + this.win.show(); + + var info = document.getElementById('download_link'); + + Ext.Ajax.request({ + url: this.sUrl, + success: function(response) { + info.innerHTML = response.responseText; + document.getElementById('download_link').focus(); + }, + failure: function(response) { + alert('Error. Couldn\'t locate download.'); + }, + params: { + action: 'fetch', + head: head, + code: this.code, + request_type: this.request_type, + request: this.request + } + }); +} + +/* +* Create the html required to display the download link +*/ +var createNotification = function() { + + if(document.getElementById('download-panel')){ + p = document.getElementById('download-panel'); + }else { + p = document.getElementById('pageBody').appendChild(document.createElement('div')); + p.id = 'download-panel'; + } + + inner = '
Download Notification
'; + inner = inner + '
'; + p.innerHTML = inner; +} + +/* +* Close the popup +*/ +var panel_close = function() { + this.win.destroy(); +} + +/** + * Defer the download to next login + */ +var deferDownload = function() { + if (confirm("This will defer the download until your next login")) { + panel_close(); + } +} + +/** + * Delete the download and close the window + */ +var deleteDownload = function() { + if (confirm("Cancelling will delete the download.\nYou will not be able to start the download at a later time.\n\nAre you sure?")) { + var info = document.getElementById('exportcode'); + + Ext.Ajax.request({ + url: this.sUrl, + success: function(response) { + if(response.responseText == 'success'){ + if(this.request_type == 'close'){ + // follow the close action + this.win.destroy(); + return; + } + } + info.innerHTML = response.responseText; + }, + failure: function(response) { + alert('Error. Couldn\'t delete download.'); + }, + params: { + action: 'delete', + code: this.code + } + }); + + panel_close(); + } +} diff --git a/sql/mysql/install/structure.sql b/sql/mysql/install/structure.sql index da9479d..ff5db61 100644 --- a/sql/mysql/install/structure.sql +++ b/sql/mysql/install/structure.sql @@ -648,7 +648,7 @@ CREATE TABLE `download_queue` ( `code` char(16) NOT NULL, `folder_id` int(11) NOT NULL, `object_id` int(11) NOT NULL, - `object_type` enum('document', 'folder') NOT NULL default 'folder', + `object_type` enum('document', 'folder', 'zip') NOT NULL default 'folder', `user_id` int(11) NOT NULL, `date_added` timestamp NOT NULL default CURRENT_TIMESTAMP, `status` tinyint(4) NOT NULL default 0, diff --git a/sql/mysql/upgrade/3.7.0.3/download_queue_zip.sql b/sql/mysql/upgrade/3.7.0.3/download_queue_zip.sql new file mode 100644 index 0000000..bbd904e --- /dev/null +++ b/sql/mysql/upgrade/3.7.0.3/download_queue_zip.sql @@ -0,0 +1 @@ +ALTER TABLE download_queue MODIFY object_type ENUM ('document', 'folder', 'zip') NOT NULL DEFAULT 'folder'; \ No newline at end of file diff --git a/templates/kt3/notifications/notification.BulkDownload.smarty b/templates/kt3/notifications/notification.BulkDownload.smarty new file mode 100644 index 0000000..bab68fa --- /dev/null +++ b/templates/kt3/notifications/notification.BulkDownload.smarty @@ -0,0 +1,16 @@ +

{$head}

+

 

+
+ {$downloadText} +
+

 

+

 

+
+ {i18n}Cancel{/i18n} +   + {i18n}Close{/i18n} +
+ + \ No newline at end of file diff --git a/templates/kt3/standard_page.smarty b/templates/kt3/standard_page.smarty index c1be49c..b89b395 100644 --- a/templates/kt3/standard_page.smarty +++ b/templates/kt3/standard_page.smarty @@ -357,3 +357,8 @@ +{if ($downloadNotification != '')} + +{/if} diff --git a/templates/ktcore/forms/widgets/imageselect.smarty b/templates/ktcore/forms/widgets/imageselect.smarty new file mode 100755 index 0000000..258cbf0 --- /dev/null +++ b/templates/ktcore/forms/widgets/imageselect.smarty @@ -0,0 +1,35 @@ +
+ +
+ + {foreach key=id item=src from=$value} + + + + + + {/foreach} + +
+ + +
+