diff --git a/_build/templates/default/sass/_trash.scss b/_build/templates/default/sass/_trash.scss new file mode 100644 index 00000000000..56c0190aecb --- /dev/null +++ b/_build/templates/default/sass/_trash.scss @@ -0,0 +1,3 @@ +.trashrow { + background-color: silver !important; +} \ No newline at end of file diff --git a/core/lexicon/en/trash.inc.php b/core/lexicon/en/trash.inc.php new file mode 100644 index 00000000000..b1157ba9161 --- /dev/null +++ b/core/lexicon/en/trash.inc.php @@ -0,0 +1,46 @@ + + Before you restore any resource, check the publishing state - you can unpublish resources here directly from the grid with a double-click on the published-cell of the resource.'; + +$_lang['trash.manage_recycle_bin_tooltip'] = "Go to the trash bin manager"; + +$_lang['trash.deletedon_title'] = 'Deleted on'; +$_lang['trash.deletedbyUser_title'] = 'Deleted by User'; + +$_lang['trash.context_title'] = 'Context'; + +$_lang['trash.purge_all'] = 'Purge all'; +$_lang['trash.restore_all'] = 'Restore all'; + +$_lang['trash.selected_purge'] = 'Purge selected resources'; +$_lang['trash.selected_restore'] = 'Restore selected resources'; + +$_lang['trash.purge'] = 'Purge resource'; +$_lang['trash.purge_confirm_title'] = 'Purge resource(s)?'; +$_lang['trash.purge_confirm_message'] = 'Do you really want to finally delete the following resource(s)? This cannot be undone.
[[+list]]'; +$_lang['trash.purgeall_confirm_message'] = 'Do you really want to finally delete the listed [[+count]] resource(s)? This cannot be undone, and affects also resources on further pages not shown here.'; +$_lang['trash.purgeall_empty_status'] = '[[+count]] resources have been finally deleted.'; + +$_lang['trash.purge_err_delete'] = '[[+count]] resources have not been purged due to errors: [[+list]]'; +$_lang['trash.purge_err_nothing'] = 'Nothing was purged, no errors occured.'; +$_lang['trash.purge_success_delete'] = '[[+count]] resources successfully purged permanently.'; + +$_lang['trash.restore'] = 'Restore resource'; +$_lang['trash.restore_confirm_title'] = 'Restore resource(s)?'; +$_lang['trash.restore_confirm_message'] = 'Do you want to restore the following resource(s)?
[[+list]]'; +$_lang['trash.restore_confirm_message_with_publish'] = 'Do you want to restore the following resource(s)?

Be aware that this will re-publish previously published resources!
[[+list]]'; diff --git a/core/model/modx/processors/resource/trash/getlist.class.php b/core/model/modx/processors/resource/trash/getlist.class.php new file mode 100644 index 00000000000..29b9a3f9297 --- /dev/null +++ b/core/model/modx/processors/resource/trash/getlist.class.php @@ -0,0 +1,139 @@ +getProperty('query'); + $c->select([ + $this->modx->getSelectColumns('modResource', 'modResource', 'modResource_'), + 'modResource_deletedbyUser' => 'User.username', + 'modResource_context_name' => 'Context.name', + ]); + $c->leftJoin('modUser', 'User', 'modResource.deletedby = User.id'); + $c->leftJoin('modContext', 'Context', 'modResource.context_key = Context.key'); + + // TODO add only resources if we have the save permission here (on the context!!) + // we need the following permissions: + // undelete_document - to restore the document + // delete_document - thats perhaps not necessary, because all documents are already deleted + // but we need the purge_deleted permission - for every single file + + if (!empty($query)) { + $c->where(array('modResource.pagetitle:LIKE' => '%' . $query . '%')); + $c->orCondition(array('modResource.longtitle:LIKE' => '%' . $query . '%')); + } + $c->where(array( + 'modResource.deleted' => true, + )); + // $c->prepare(); + // $this->modx->log(1,"Query: ".$c->toSQL()); + return $c; + } + + public function prepareRow(xPDOObject $object) { + // quick exit if we don't have access to the context + // this is a strange workaround: obviously we can access the resources even if we don't have access to the context! Check that + // TODO check if that is the same for resource groups + $context = $this->modx->getContext($object->get('context_key')); + if (!$context) return []; + + $charset = $this->modx->getOption('modx_charset', null, 'UTF-8'); + $objectArray = $object->toArray(); + $objectArray['pagetitle'] = htmlentities($objectArray['pagetitle'], ENT_COMPAT, $charset); + + // to enable a better detection of the resource's location, we also construct the + // parent-child path to the resource + + $parents = array(); + $parent = $objectArray['parent']; + + while ($parent!=0) { + $parents[] = $this->modx->getObject('modResource', $parent); + $parent = end($parents)->get('parent'); + } + + $parentPath = ""; + foreach ($parents as $parent) { + $parentPath = $parent->get('pagetitle') . " (".$parent->get('id') . ") > " . $parentPath; + } + $objectArray['parentPath'] = "[" . $objectArray['context_key'] . "] " . $parentPath; + + // TODO implement permission checks for every resource and return only resources user is allowed to see + + // show the permissions for the context + $canView = $this->modx->hasPermission('view'); + $canPurge = $this->modx->hasPermission('purge_deleted'); + $canUndelete = $this->modx->hasPermission('undelete_document'); + $canPublish = $this->modx->hasPermission('publish'); + $canSave = $this->modx->hasPermission('save'); + $canEdit = $this->modx->hasPermission('edit'); + $canList = $this->modx->hasPermission('list'); + $canLoad = $this->modx->hasPermission('load'); + + $objectArray['iconCls'] = $this->modx->getOption('mgr_source_icon', null, 'icon-folder-open-o'); + + $cls = array(); + $cls[] = 'restore'; + $cls[] = 'purge'; + $cls[] = 'undelete_document'; + + $cls = array(); + if ($object->checkPolicy('purge_deleted') && $canSave && $canEdit && $canPurge) { + $cls[] = 'trashpurge'; + } + if ($object->checkPolicy('undelete_document') && $canSave && $canEdit) { + $cls[] = 'trashundelete'; + } + if ($object->checkPolicy('save') && $canSave && $canEdit) { + $cls[] = 'trashsave'; + } + if ($object->checkPolicy('edit') && $canSave && $canEdit) { + $cls[] = 'trashedit'; + } + $cls[] = 'trashrow'; + + + $debug = array('id'=>$object->get('id'), 'pagetitle'=>$object->get('pagetitle')); + $this->modx->log(1,"array: ".print_r($debug,true)); + $this->modx->log(1,"Can list: ".$canList.", list:".$object->checkPolicy('list')); + $this->modx->log(1,"Can load: ".$canLoad.", load: ".$object->checkPolicy('load')); + $this->modx->log(1,"Can view: ".$canView.", view_doc:".$object->checkPolicy('view_document').", view: ".$object->checkPolicy('view')); + $this->modx->log(1,"Can save: ".$canSave.", save_doc:".$object->checkPolicy('save_document').", save: ".$object->checkPolicy('save')); + $this->modx->log(1,"Can edit: ".$canEdit.", edit_doco:".$object->checkPolicy('edit_document').", edit: ".$object->checkPolicy('edit')); + $this->modx->log(1,"Can purge: ".$canPurge.", purge_deleted:".$object->checkPolicy('purge_deleted')); + + $this->modx->log(1,"context: ".$object->get('context_key')); + + $objectArray['cls'] = implode(' ', $cls); + + return $objectArray; + + + } +} + +return 'modResourceTrashGetListProcessor'; + diff --git a/core/model/modx/processors/resource/trash/purge.class.php b/core/model/modx/processors/resource/trash/purge.class.php new file mode 100644 index 00000000000..63ae8e46103 --- /dev/null +++ b/core/model/modx/processors/resource/trash/purge.class.php @@ -0,0 +1,210 @@ +modx->hasPermission('purge_deleted'); + } + + public function getLanguageTopics() { + return array( 'resource','trash' ); + } + + /** + * @return bool|null|string + */ + public function initialize() { + $idlist = $this->getProperty('ids', false); + //$this->modx->log(modX::LOG_LEVEL_DEBUG, "[purge] purging ids: ".$idlist); + if (!$idlist) { + return $this->modx->lexicon('resource_err_ns'); + } + if ($idlist==-1) { + $this->modx->log(modX::LOG_LEVEL_WARN, "[purge] purging everything we are allowed to."); + $this->resources = $this->modx->getCollection('modResource', array( + 'deleted' => true, + ) + ); + $this->modx->log(modX::LOG_LEVEL_WARN, "[purge] got ".count($this->resources)." overall deleted resources. Checking permissions..."); + + } else { + // we have an explicit selection of ids here + $this->ids = explode(',', $idlist); + $this->modx->log(modX::LOG_LEVEL_WARN, "[purge] purging ".count($this->ids)." resources: ".$idlist); + + //$this->resource = $this->modx->getObject( 'modResource', $this->id ); + //if ( empty( $this->resource ) ) return $this->modx->lexicon( 'resource_err_nfs', array( 'id' => $this->id ) ); + + $this->resources = $this->modx->getCollection('modResource', array( + 'deleted' => true, + 'id:IN' => $this->ids + ) + ); + } + + /* validate resource can be deleted: this is necessary in advance, because + otherwise the tvs might already have been removed, when the policy on the + resource is checked. (just a guess, does not harm to check here and again on + processing. + */ + // TODO instead of throwing an error here, we could silently continue + $this->failures = array(); + $success = array(); + $policies_needed = array( + 'save' => true, + 'delete' => true, + 'load' => true, + 'list' => true, + 'edit' => true, + ); + foreach ($this->resources as $resource) { + $context_allowed = $this->modx->getContext($resource->get('context_key')); + $policy_allowed = $resource->checkPolicy($policies_needed); + + // again, if we do not want to allow deleting of resources in contexts we are not allowed to see, we have to check that manually + // this _should_ be done by the resources checkPolicy + if (!$context_allowed) { + $this->modx->log(modX::LOG_LEVEL_WARN, + "[purge] context access denied for resource " . $resource->id ." in context ".$resource->get('context_key')); + $this->failures[] = $resource->id; + } + if (!$policy_allowed) { + $this->modx->log(modX::LOG_LEVEL_WARN, + "[purge] permissions denied for resource " . $resource->id . ": save=" . !$resource->checkPolicy(array('save')) . ", delete=" . $resource->checkPolicy(array('delete'))); + $this->failures[] = $resource->id; + } + if ($policy_allowed && $context_allowed) { + $this->modx->log(modX::LOG_LEVEL_WARN, "[purge] all resource and context permissions ok for resource " . $resource->id); + $success[] = $resource->id; + } + } + // we refresh the resources list here for the processor + $this->ids = $success; + if (empty($success)) { + $this->resources = array(); + } else { + $this->resources = $this->modx->getCollection('modResource', array( + 'deleted' => true, + 'id:IN' => $success + ) + ); + } + return true; + } + + public function process() { + $count = count($this->resources); + + //$this->modx->log(MODx::LOG_LEVEL_INFO, "Purging resources: " . implode(',', $this->ids)); + // fire before empty trash event + $this->modx->invokeEvent('OnBeforeEmptyTrash', array( + 'ids' => &$this->ids, + 'resources' => &$this->resources, + )); + + //reset($this->resources); + + // we track success and failure independently, as we don't want + // to stop in case of single files failing + $success = array(); + + $this->failures = array(); // we are no more interested in the previous failures, as they are already filtered out + + $permissionsForPurge = array( + 'save' => true, + 'delete' => true, + ); + + /** @var modResource $resource */ + foreach ($this->resources as $resource) { + if (!$resource->checkPolicy($permissionsForPurge)) { + continue; + } + + $id = $resource->get('id'); + + $resourceGroupResources = $resource->getMany('ResourceGroupResources'); + $templateVarResources = $resource->getMany('TemplateVarResources'); + + /** @var modResourceGroupResource $resourceGroupResource */ + foreach ($resourceGroupResources as $resourceGroupResource) { + $resourceGroupResource->remove(); + } + + /** @var modTemplateVarResource $templateVarResource */ + foreach ($templateVarResources as $templateVarResource) { + $templateVarResource->remove(); + } + + // TODO isn't that a problem here? + // If resource remove now fails we already removed the tvs! + // shouldn't resource->remove also take care of the tvs and resource groups? + if ($resource->remove()==false) { + // we just add the id to the failures here (we may already have failures from the init with permissions) + $this->failures[] = $id; + } else { + $success[] = $id; + } + } + + $this->modx->invokeEvent('OnEmptyTrash', array( + 'num_deleted' => sizeof($success), + 'resources' => &$this->resources, + 'ids' => &$success, + )); + + $this->modx->logManagerAction('empty_trash', 'modResource', implode(',', $success)); + + // if nothing was successfully purged, we throw a failure here + if (count($this->failures)>0 && count($success)==0) { + return $this->failure($this->modx->lexicon('trash.purge_err_delete', array( + // TODO get the pagetitles here + 'list' => implode(',',$this->failures), + 'count' => count($this->failures) + ))); + } + + if (count($this->failures)==0 && count($success)==0) { + return $this->success($this->modx->lexicon('trash.purge_err_nothing')); + } + + $msg = "no message"; + if (sizeof($success)>0) { + $this->modx->log(modX::LOG_LEVEL_DEBUG, "Clearing cache after purging operation."); + $this->modx->cacheManager->refresh(); + $msg = $this->modx->lexicon('trash.purge_success_delete', array( + 'list' => implode(',',$success), + 'count' => count($success) + )); + if (count($this->failures)>0) { + $msg .= '
'.$this->modx->lexicon('trash.purge_err_delete', array( + // TODO get the pagetitles here + 'list' => implode(',',$this->failures), + 'count' => count($this->failures) + )); + } + } + + return $this->success($msg, array('count_success'=>count($success), 'count_failures' => count($this->failures))); + } +} + +return 'modResourceTrashPurgeProcessor'; \ No newline at end of file diff --git a/core/model/modx/processors/resource/trash/restore.class.php b/core/model/modx/processors/resource/trash/restore.class.php new file mode 100644 index 00000000000..de222a7294d --- /dev/null +++ b/core/model/modx/processors/resource/trash/restore.class.php @@ -0,0 +1,174 @@ +idList = explode(',', $this->getProperty('ids', false)); + $this->modx->log(1, "Restorelist: " . print_r($this->idList, true)); + $this->contexts = array(); + + if (count($this->idList)===0) { + return $this->modx->lexicon('resource_err_ns'); + } + + // and now retrieve a collection of resources here + $this->resourceList = $this->modx->getCollection('modResource', array( + 'deleted' => true, + 'id:IN' => $this->idList, + )); + $count = sizeof($this->resourceList); + + if (empty($this->resourceList)) { + return $this->modx->lexicon('resource_err_nfs', array('ids' => $this->idList)); + } + + $this->modx->log(1, "found " . $count); + + foreach ($this->resourceList as $resource) { + $this->modx->log(1, "restoring " . $resource->get('id') . ": " . $resource->get('pagetitle')); + } + + /* validate resource can be deleted */ + //if (!$this->resource->checkPolicy(array('save' => true, 'delete' => true))) { + // return $this->modx->lexicon('permission_denied'); + //} + return true; + } + + public function process() + { + foreach ($this->resourceList as $resource) { + $this->resource = $resource; + $this->contexts[] = ($this->resource->get('context_key')); + + if (!$this->addLock()) { + return $this->failure($this->modx->lexicon('resource_locked_by', + array('id' => $this->resource->get('id'), 'user' => $this->lockedUser->get('username')))); + } + + /* 'undelete' the resource. */ + $this->resource->set('deleted', false); + $this->resource->set('deletedby', 0); + $this->resource->set('deletedon', 0); + + if ($this->resource->save()==false) { + $this->resource->removeLock(); + + return $this->failure($this->modx->lexicon('resource_err_undelete')); + } + + // TODO this still has to be discussed: what happens to the children? + //$this->unDeleteChildren($this->resource->get('id'), $this->resource->get('deletedon')); + + $this->fireAfterUnDeleteEvent(); + + /* log manager action */ + $this->logManagerAction(); + $this->removeLock(); + } + /* empty cache */ + $this->clearCache(); + + $deletedCount = $this->modx->getCount('modResource', array('deleted' => 1)); + + $outputArray = $this->resource->get(array('id')); + + $outputArray['deletedCount'] = $deletedCount; + + return $this->modx->error->success('', $outputArray); + } + + /** + * Add a lock to the Resource while undeleting it + * + * @return boolean + */ + public function addLock() + { + $locked = $this->resource->addLock(); + if ($locked!==true) { + $user = $this->modx->getObject('modUser', $locked); + if ($user) { + $locked = false; + } + } + + return $locked; + } + + /** + * Remove the lock from the Resource + * + * @return boolean + */ + public function removeLock() + { + return $this->resource->removeLock(); + } + + /** + * Fire the UnDelete event + * + * @return void + */ + public function fireAfterUnDeleteEvent() + { + $this->modx->invokeEvent('OnResourceUndelete', array( + 'id' => $this->resource->get('id'), + 'resource' => &$this->resource, + )); + } + + /** + * Log the manager action + * + * @return void + */ + public function logManagerAction() + { + $this->modx->logManagerAction('undelete_resource', 'modResource', $this->resource->get('id')); + } + + /** + * Clear the site cache for the restored resources contexts + * + * @return void + */ + public function clearCache() + { + $this->modx->log(modX::LOG_LEVEL_DEBUG, 'Refreshing contexts of restored resources: '.print_r($this->contexts,true)); + $this->modx->cacheManager->refresh(array( + 'db' => array(), + 'auto_publish' => array('contexts' => $this->contexts), + 'context_settings' => array('contexts' => $this->contexts), + 'resource' => array('contexts' => $this->contexts), + )); + } +} + +return 'modResourceTrashRestoreProcessor'; \ No newline at end of file diff --git a/manager/assets/modext/widgets/resource/modx.panel.trash.js b/manager/assets/modext/widgets/resource/modx.panel.trash.js new file mode 100644 index 00000000000..640236903a9 --- /dev/null +++ b/manager/assets/modext/widgets/resource/modx.panel.trash.js @@ -0,0 +1,389 @@ +MODx.panel.Trash = function (config) { + config = config || {}; + Ext.applyIf(config, { + id: 'modx-panel-trash' + , cls: 'container' + , bodyStyle: '' + , defaults: {collapsible: false, autoHeight: true} + , items: [{ + html: _('trash.page_title') + , id: 'modx-trash-header' + , xtype: 'modx-header' + }, MODx.getPageStructure([{ + layout: 'form' + , title: _('trash.tab_title') + , items: [{ + html: '

' + _('trash.intro_msg') + '

' + , xtype: 'modx-description' + }, { + xtype: 'modx-grid-trash' + , id: 'modx-trash-resourcelist' + , cls: 'main-wrapper' + , preventRender: true + }] + }], { + stateful: true + , stateId: 'modx-trash-tabpanel' + , stateEvents: ['tabchange'] + , getState: function () { + return {activeTab: this.items.indexOf(this.getActiveTab())}; + } + })] + }); + MODx.panel.Trash.superclass.constructor.call(this, config); + this.addEvents('emptyTrash'); +}; +Ext.extend(MODx.panel.Trash, MODx.FormPanel); +Ext.reg('modx-panel-trash', MODx.panel.Trash); + +MODx.grid.Trash = function (config) { + config = config || {}; + + this.sm = new Ext.grid.CheckboxSelectionModel(); + Ext.applyIf(config, { + url: MODx.config.connector_url + ,baseParams: { + action: 'resource/trash/getlist' + } + ,fields: [ + 'id', + 'context_key', + 'parentPath', + 'pagetitle', + 'longtitle', + 'published', + 'deletedon', + 'cls', + 'deletedbyUser', + 'context_name'] + ,paging: true + + // when we change e.g. the publishing state of a deleted resource, we refresh the tree to reflect + // the changes done + ,autosave: true + ,save_action: 'resource/updatefromgrid' + ,save_callback: function() { + this.refreshEverything(); + } + + ,remoteSort: true + ,sm: this.sm + ,columns: [this.sm, { + header: _('id') + , dataIndex: 'id' + , width: 20 + , sortable: true + }, { + header: _('trash.context_title') + , dataIndex: 'context_name' + , width: 60 + , sortable: false + }, { + header: _('pagetitle') + , dataIndex: 'pagetitle' + , width: 80 + , sortable: true + , tooltip: "TODO: longtitle here" + }, { + header: _('long_title') + , dataIndex: 'longtitle' + , width: 120 + , sortable: false + }, { + header: _('published') + , dataIndex: 'published' + , width: 40 + , sortable: false + , editor: {xtype: 'combo-boolean', renderer: 'boolean'} + }, { + header: _('trash.deletedon_title') + , dataIndex: 'deletedon' + , width: 75 + , sortable: false + }, { + /* header: _('trash.deletedby_title') + , dataIndex: 'deletedby' + , width: 40 + , sortable: false + }, { + */ header: _('trash.deletedbyUser_title') + , dataIndex: 'deletedbyUser' + , width: 40 + , sortable: false + }] + , + tbar: [{ + text: _('bulk_actions') + , menu: [{ + text: _('trash.selected_purge') + , handler: this.purgeSelected + , scope: this + }, { + text: _('trash.selected_restore') + , handler: this.restoreSelected + , scope: this + }] + }, { + xtype: 'button' + , text: _('trash.purge_all') + , id: 'modx-purge-all' + , cls: 'x-form-purge-all red' + , listeners: { + 'click': {fn: this.purgeAll, scope: this} + } + }, { + xtype: 'button' + , text: _('trash.restore_all') + , id: 'modx-restore-all' + , cls: 'x-form-restore-all green' + , listeners: { + 'click': {fn: this.restoreAll, scope: this} + } + }, '->', { + xtype: 'textfield' + , name: 'search' + , id: 'modx-source-search' + , cls: 'x-form-filter' + , emptyText: _('search_ellipsis') + , listeners: { + 'change': {fn: this.search, scope: this} + , 'render': { + fn: function (cmp) { + new Ext.KeyMap(cmp.getEl(), { + key: Ext.EventObject.ENTER + , fn: this.blur + , scope: cmp + }); + }, scope: this + } + } + }, { + xtype: 'button' + , text: _('filter_clear') + , id: 'modx-filter-clear' + , cls: 'x-form-filter-clear' + , listeners: { + 'click': {fn: this.clearFilter, scope: this} + } + }] + }); + MODx.grid.Trash.superclass.constructor.call(this, config); +}; + +Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { + getMenu: function () { + var r = this.getSelectionModel().getSelected(); + var p = r.data.cls; + + var m = []; + if (this.getSelectionModel().getCount() > 1) { + m.push({ + text: _('trash.selected_purge') + , handler: this.purgeSelected + , scope: this + }); + m.push({ + text: _('trash.selected_restore') + , handler: this.restoreSelected + , scope: this + }); + } else { + if (p.indexOf('purge') !== -1) { + m.push({ + text: _('trash.purge') + , handler: this.purgeResource + }); + } + if (p.indexOf('restore') !== -1) { + m.push({ + text: _('trash.restore') + , handler: this.restoreResource + }); + } + } + if (m.length > 0) { + this.addContextMenuItem(m); + } + } + , purgeResource: function () { + MODx.msg.confirm({ + title: _('trash.purge_confirm_title') + , text: _('trash.purge_confirm_message', { + 'list': this.listResources('
') + }) + , url: this.config.url + , params: { + action: 'resource/trash/purge' + , id: this.menu.record.id + } + , listeners: { + 'success': { + fn: function (data) { + this.refreshEverything(data.total); + }, scope: this + } + } + }); + } + , restoreResource: function () { + console.log(this.menu.record.published); + var withPublish = ''; + if (this.menu.record.published) withPublish = '_with_publish'; + MODx.msg.confirm({ + title: _('trash.restore_confirm_title') + , text: _('trash.restore_confirm_message' + withPublish, { + 'list': this.listResources('
') + }) + , url: this.config.url + , params: { + action: 'resource/undelete' + , id: this.menu.record.id + } + , listeners: { + 'success': { + fn: function (data) { + this.refreshEverything(data.total); + }, scope: this + } + } + }); + } + , purgeSelected: function () { + var cs = this.getSelectedAsList(); + if (cs === false) return false; + + MODx.msg.confirm({ + title: _('trash.purge_confirm_title') + , text: _('trash.purge_confirm_message', { + 'list': this.listResources('') + }) + , url: this.config.url + , params: { + action: 'resource/trash/purge' + , ids: cs + } + , listeners: { + 'success': { + fn: function (data) { + this.getSelectionModel().clearSelections(true); + this.refreshEverything(data.total); + }, scope: this + } + } + }); + return true; + } + + , purgeAll: function () { + MODx.msg.confirm({ + title: _('trash.purge_confirm_title') + , text: _('trash.purgeall_confirm_message', { + 'count': this.listResources('') + }) + , url: this.config.url //MODx.config.connector_url + , params: { + action: 'resource/trash/purge' + , ids: -1 // this causes the processor to delete everything you have access to + } + , listeners: { + 'success': { + fn: function (data) { + MODx.msg.status({ + title: _('success') + , message: data.message + }); + if (data.object.count_success > 0) { + this.refreshEverything(data.total); // no need to refresh if nothing was purged + this.fireEvent('emptyTrash'); + } + }, scope: this + }, + 'error': { + fn: function (data) { + MODx.msg.status({ + title: _('error') + , message: data.message + }); + }, scope: this + } + } + }) + } + + , refreshTree: function() { + var t = Ext.getCmp('modx-resource-tree'); + t.refresh(); + this.refreshRecycleBinButton(); + } + + , refreshEverything: function(total) { + this.refresh(); + this.refreshTree(); + this.refreshRecycleBinButton(total); + } + + , refreshRecycleBinButton: function(total) { + var t = Ext.getCmp('modx-resource-tree'); + var trashButton = t.getTopToolbar().findById('emptifier'); + + var count = this.getStore().getTotalCount(); + console.log("Items left: " + total) + // TODO we need to now the number of deleted resources here. + + // if no resource is deleted, we disable the icon. + // otherwise we hae to update the tooltip + if (total !== undefined) { + if (total = 0) { + trashButton.disable(); + trashButton.setTooltip(_('trash.tooltip_recycle_bin_empty')); + } else { + trashButton.enable(); + trashButton.setTooltip(_('empty_recycle_bin') + ' (' + total + ' resources)'); + } + } + } + + , restoreSelected: function () { + var cs = this.getSelectedAsList(); + if (cs === false) return false; + + MODx.msg.confirm({ + title: _('trash.restore_confirm_title') + , text: _('trash.restore_confirm_message', { + 'list': this.listResources('') + }) + , url: this.config.url + , params: { + action: 'resource/trash/restore', + ids: cs + } + , listeners: { + 'success': { + fn: function (data) { + this.refreshEverything(data.total); + }, scope: this + } + } + }); + return true; + } + , listResources: function (separator) { + if (separator === undefined) separator = ','; + + // creates a textual representation of the selected resources + // we create a textlist of the resources here to show them again in the confirmation box + var selections = this.getSelectionModel().getSelections(); + var text = [], t; + selections.forEach(function (selection) { + //t = selection.data.pagetitle + " (" + selection.data.id + ")"; + t = selection.data.parentPath + "" + selection.data.pagetitle + " (" + selection.data.id + ")" + ""; + if (selection.data.published) { + t = '' + t + ''; + } + t = "
" + t + "
"; + text.push(t); + }); + return text.join(separator); + } +}); +Ext.reg('modx-grid-trash', MODx.grid.Trash); \ No newline at end of file diff --git a/manager/assets/modext/widgets/resource/modx.panel.tree.resource.js b/manager/assets/modext/widgets/resource/modx.panel.tree.resource.js new file mode 100644 index 00000000000..69579f55640 --- /dev/null +++ b/manager/assets/modext/widgets/resource/modx.panel.tree.resource.js @@ -0,0 +1,1371 @@ +/** + * Generates the Resource Tree in Ext + * + * @class MODx.tree.Resource + * @extends MODx.tree.Tree + * @param {Object} config An object of options. + * @xtype modx-tree-resource + */ +MODx.tree.Resource = function(config) { + config = config || {}; + Ext.applyIf(config,{ + url: MODx.config.connector_url + ,action: 'resource/getNodes' + ,title: '' + ,rootVisible: false + ,expandFirst: true + ,enableDD: (MODx.config.enable_dragdrop != '0') ? true : false + ,ddGroup: 'modx-treedrop-dd' + ,remoteToolbar: true + ,remoteToolbarAction: 'resource/gettoolbar' + ,sortAction: 'resource/sort' + ,sortBy: this.getDefaultSortBy(config) + ,tbarCfg: { + // hidden: true + id: config.id ? config.id+'-tbar' : 'modx-tree-resource-tbar' + } + ,baseParams: { + sortBy: this.getDefaultSortBy(config) + ,currentResource: MODx.request.id || 0 + ,currentAction: MODx.request.a || 0 + } + }); + MODx.tree.Resource.superclass.constructor.call(this,config); + this.addEvents('loadCreateMenus', 'emptyTrash'); + this.on('afterSort',this._handleAfterDrop,this); +}; +Ext.extend(MODx.tree.Resource,MODx.tree.Tree,{ + forms: {} + ,windows: {} + ,stores: {} + + ,_initExpand: function() { + var treeState = Ext.state.Manager.get(this.treestate_id); + if ((Ext.isString(treeState) || Ext.isEmpty(treeState)) && this.root) { + if (this.root) {this.root.expand();} + var wn = this.getNodeById('web_0'); + if (wn && this.config.expandFirst) { + wn.select(); + wn.expand(); + } + } else { + // If we have disabled context sort, make sure dragging and dropping is disabled on the root elements + // in the tree. This corresponds to the context nodes. + if (MODx.config.context_tree_sort !== '1') { + if (typeof(this.root) !== 'undefined' && typeof(this.root.childNodes) !== 'undefined') { + for (var i = 0; i < this.root.childNodes.length; i++) { + this.root.childNodes[i].draggable = false; + } + } + } + + for (var i=0;i]+)>)/ig,"")); + } + } + } + + ,_handleDrop: function(e){ + var dropNode = e.dropNode; + var targetParent = e.target; + + if (targetParent.findChild('id',dropNode.attributes.id) !== null) {return false;} + + if (dropNode.attributes.type == 'modContext' && (targetParent.getDepth() > 1 || (targetParent.attributes.id == targetParent.attributes.pk + '_0' && e.point == 'append'))) { + return false; + } + + if (dropNode.attributes.type !== 'modContext' && targetParent.getDepth() <= 1 && e.point !== 'append') { + return false; + } + + if (MODx.config.resource_classes_drop[targetParent.attributes.classKey] == undefined) { + if (targetParent.attributes.hide_children_in_tree) { return false; } + } else if (MODx.config.resource_classes_drop[targetParent.attributes.classKey] == 0) { + return false; + } + + return dropNode.attributes.text != 'root' && dropNode.attributes.text !== '' + && targetParent.attributes.text != 'root' && targetParent.attributes.text !== ''; + } + + ,getContextSettingForNode: function(node,ctx,setting,dv) { + var val = dv || null; + if (node.attributes.type != 'modContext') { + var t = node.getOwnerTree(); + var rn = t.getRootNode(); + var cn = rn.findChild('ctx',ctx,false); + if (cn) { + val = cn.attributes.settings[setting]; + } + } else { + val = node.attributes.settings[setting]; + } + return val; + } + + ,quickCreate: function(itm,e,cls,ctx,p) { + cls = cls || 'modDocument'; + var r = { + class_key: cls + ,context_key: ctx || 'web' + ,'parent': p || 0 + ,'template': parseInt(this.getContextSettingForNode(this.cm.activeNode,ctx,'default_template',MODx.config.default_template)) + ,'richtext': parseInt(this.getContextSettingForNode(this.cm.activeNode,ctx,'richtext_default',MODx.config.richtext_default)) + ,'hidemenu': parseInt(this.getContextSettingForNode(this.cm.activeNode,ctx,'hidemenu_default',MODx.config.hidemenu_default)) + ,'searchable': parseInt(this.getContextSettingForNode(this.cm.activeNode,ctx,'search_default',MODx.config.search_default)) + ,'cacheable': parseInt(this.getContextSettingForNode(this.cm.activeNode,ctx,'cache_default',MODx.config.cache_default)) + ,'published': parseInt(this.getContextSettingForNode(this.cm.activeNode,ctx,'publish_default',MODx.config.publish_default)) + ,'content_type': parseInt(this.getContextSettingForNode(this.cm.activeNode,ctx,'default_content_type',MODx.config.default_content_type)) + }; + if (this.cm.activeNode.attributes.type != 'modContext') { + var t = this.cm.activeNode.getOwnerTree(); + var rn = t.getRootNode(); + var cn = rn.findChild('ctx',ctx,false); + if (cn) { + r['template'] = cn.attributes.settings.default_template; + } + } else { + r['template'] = this.cm.activeNode.attributes.settings.default_template; + } + + var w = MODx.load({ + xtype: 'modx-window-quick-create-modResource' + ,record: r + ,listeners: { + 'success':{ + fn: function() { + this.refreshNode(this.cm.activeNode.id, this.cm.activeNode.childNodes.length > 0); + } + ,scope: this} + ,'hide':{fn:function() {this.destroy();}} + ,'show':{fn:function() {this.center();}} + } + }); + w.setValues(r); + w.show(e.target,function() { + Ext.isSafari ? w.setPosition(null,30) : w.center(); + },this); + } + + ,quickUpdate: function(itm,e,cls) { + MODx.Ajax.request({ + url: MODx.config.connector_url + ,params: { + action: 'resource/get' + ,id: this.cm.activeNode.attributes.pk + ,skipFormatDates: true + } + ,listeners: { + 'success': {fn:function(r) { + var pr = r.object; + pr.class_key = cls; + + var w = MODx.load({ + xtype: 'modx-window-quick-update-modResource' + ,record: pr + ,listeners: { + 'success':{fn:function(r) { + this.refreshNode(this.cm.activeNode.id); + var newTitle = '' + r.f.findField('pagetitle').getValue() + ' (' + w.record.id + ')'; + w.setTitle(w.title.replace(//, newTitle)); + },scope:this} + ,'hide':{fn:function() {this.destroy();}} + } + }); + w.title += ': ' + w.record.pagetitle + ' ('+ w.record.id + ')'; + w.setValues(r.object); + w.show(e.target,function() { + Ext.isSafari ? w.setPosition(null,30) : w.center(); + },this); + },scope:this} + } + }); + } + + ,_getModContextMenu: function(n) { + var a = n.attributes; + var ui = n.getUI(); + var m = []; + + m.push({ + text: ''+a.text+'' + ,handler: function() {return false;} + ,header: true + }); + m.push('-'); + if (ui.hasClass('pedit')) { + m.push({ + text: _('edit_context') + ,handler: function() { + var at = this.cm.activeNode.attributes; + this.loadAction('a=context/update&key='+at.pk); + } + }); + } + m.push({ + text: _('context_refresh') + ,handler: function() { + this.refreshNode(this.cm.activeNode.id,true); + } + }); + if (ui.hasClass('pnewdoc')) { + m.push('-'); + this._getCreateMenus(m,'0',ui); + } + if (ui.hasClass('pnew')) { + m.push({ + text: _('context_duplicate') + ,handler: this.duplicateContext + }); + } + if (ui.hasClass('pdelete')) { + m.push('-'); + m.push({ + text: _('context_remove') + ,handler: this.removeContext + }); + } + + if(!ui.hasClass('x-tree-node-leaf')) { + m.push('-'); + m.push(this._getSortMenu()); + } + + return m; + } + + ,overviewResource: function() {this.loadAction('a=resource/data')} + + ,quickUpdateResource: function(itm,e) { + this.quickUpdate(itm,e,itm.classKey); + } + + ,editResource: function() {this.loadAction('a=resource/update');} + + ,_getModResourceMenu: function(n) { + var a = n.attributes; + var ui = n.getUI(); + var m = []; + m.push({ + text: ''+a.text+'' + ,handler: function() {return false;} + ,header: true + }); + m.push('-'); + if (ui.hasClass('pview')) { + m.push({ + text: _('resource_overview') + ,handler: this.overviewResource + }); + } + if (ui.hasClass('pedit')) { + m.push({ + text: _('resource_edit') + ,handler: this.editResource + }); + } + if (ui.hasClass('pqupdate')) { + m.push({ + text: _('quick_update_resource') + ,classKey: a.classKey + ,handler: this.quickUpdateResource + }); + } + if (ui.hasClass('pduplicate')) { + m.push({ + text: _('resource_duplicate') + ,handler: this.duplicateResource + }); + } + m.push({ + text: _('resource_refresh') + ,handler: this.refreshResource + ,scope: this + }); + + if (ui.hasClass('pnew')) { + m.push('-'); + this._getCreateMenus(m,null,ui); + } + + if (ui.hasClass('psave')) { + m.push('-'); + if (ui.hasClass('ppublish') && ui.hasClass('unpublished')) { + m.push({ + text: _('resource_publish') + ,handler: this.publishDocument + }); + } else if (ui.hasClass('punpublish')) { + m.push({ + text: _('resource_unpublish') + ,handler: this.unpublishDocument + }); + } + if (ui.hasClass('pundelete') && ui.hasClass('deleted')) { + m.push({ + text: _('resource_undelete') + ,handler: this.undeleteDocument + }); + } else if (ui.hasClass('pdelete') && !ui.hasClass('deleted')) { + m.push({ + text: _('resource_delete') + ,handler: this.deleteDocument + }); + } + } + + if(!ui.hasClass('x-tree-node-leaf')) { + m.push('-'); + m.push(this._getSortMenu()); + } + + if (ui.hasClass('pview') && a.preview_url != '') { + m.push('-'); + m.push({ + text: _('resource_view') + ,handler: this.preview + }); + } + return m; + } + + ,refreshResource: function() { + this.refreshNode(this.cm.activeNode.id); + } + + ,createResourceHere: function(itm) { + var at = this.cm.activeNode.attributes; + var p = itm.usePk ? itm.usePk : at.pk; + this.loadAction( + 'a=resource/create&class_key=' + itm.classKey + '&parent=' + p + (at.ctx ? '&context_key='+at.ctx : '') + ); + } + + ,createResource: function(itm,e) { + var at = this.cm.activeNode.attributes; + var p = itm.usePk ? itm.usePk : at.pk; + this.quickCreate(itm,e,itm.classKey,at.ctx,p); + } + + ,_getCreateMenus: function(m,pk,ui) { + var types = MODx.config.resource_classes; + var o = this.fireEvent('loadCreateMenus',types); + if (Ext.isObject(o)) { + Ext.apply(types,o); + } + var coreTypes = ['modDocument','modWebLink','modSymLink','modStaticResource']; + var ct = []; + var qct = []; + for (var k in types) { + if (coreTypes.indexOf(k) != -1) { + if (!ui.hasClass('pnew_'+k)) { + continue; + } + } + ct.push({ + text: types[k]['text_create_here'] + ,classKey: k + ,usePk: pk ? pk : false + ,handler: this.createResourceHere + ,scope: this + }); + if (ui && ui.hasClass('pqcreate')) { + qct.push({ + text: types[k]['text_create'] + ,classKey: k + ,handler: this.createResource + ,scope: this + }); + } + } + m.push({ + text: _('create') + ,handler: function() {return false;} + ,menu: {items: ct} + }); + if (ui && ui.hasClass('pqcreate')) { + m.push({ + text: _('quick_create') + ,handler: function() {return false;} + ,menu: {items: qct} + }); + } + + return m; + } + + /** + * Handles all drag events into the tree. + * @param {Object} dropEvent The node dropped on the parent node. + */ + ,_handleDrag: function(dropEvent) { + function simplifyNodes(node) { + var resultNode = {}; + var kids = node.childNodes; + var len = kids.length; + for (var i = 0; i < len; i++) { + resultNode[kids[i].id] = simplifyNodes(kids[i]); + } + return resultNode; + } + + var encNodes = Ext.encode(simplifyNodes(dropEvent.tree.root)); + this.fireEvent('beforeSort',encNodes); + MODx.Ajax.request({ + url: this.config.url + ,params: { + target: dropEvent.target.attributes.id + ,source: dropEvent.source.dragData.node.attributes.id + ,point: dropEvent.point + ,data: encodeURIComponent(encNodes) + ,action: this.config.sortAction || 'sort' + } + ,listeners: { + 'success': {fn:function(r) { + var el = dropEvent.dropNode.getUI().getTextEl(); + if (el) {Ext.get(el).frame();} + this.fireEvent('afterSort',{event:dropEvent,result:r}); + },scope:this} + ,'failure': {fn:function(r) { + MODx.form.Handler.errorJSON(r); + this.refresh(); + return false; + },scope:this} + } + }); + } + + ,_getSortMenu: function(){ + return [{ + text: _('sort_by') + ,handler: function() {return false;} + ,menu: { + items:[{ + text: _('tree_order') + ,sortBy: 'menuindex' + ,sortDir: 'ASC' + ,handler: this.filterSort + ,scope: this + },{ + text: _('recently_updated') + ,sortBy: 'editedon' + ,sortDir: 'ASC' + ,handler: this.filterSort + ,scope: this + },{ + text: _('newest') + ,sortBy: 'createdon' + ,sortDir: 'DESC' + ,handler: this.filterSort + ,scope: this + },{ + text: _('oldest') + ,sortBy: 'createdon' + ,sortDir: 'ASC' + ,handler: this.filterSort + ,scope: this + },{ + text: _('publish_date') + ,sortBy: 'pub_date' + ,sortDir: 'ASC' + ,handler: this.filterSort + ,scope: this + },{ + text: _('unpublish_date') + ,sortBy: 'unpub_date' + ,sortDir: 'ASC' + ,handler: this.filterSort + ,scope: this + },{ + text: _('publishedon') + ,sortBy: 'publishedon' + ,sortDir: 'ASC' + ,handler: this.filterSort + ,scope: this + },{ + text: _('title') + ,sortBy: 'pagetitle' + ,sortDir: 'ASC' + ,handler: this.filterSort + ,scope: this + },{ + text: _('alias') + ,sortBy: 'alias' + ,sortDir: 'ASC' + ,handler: this.filterSort + ,scope: this + }] + } + }]; + } + + ,handleCreateClick: function(node){ + this.cm.activeNode = node; + var itm = { + usePk: '0' + ,classKey: 'modDocument' + }; + + this.createResourceHere(itm); + } +}); +Ext.reg('modx-tree-resource',MODx.tree.Resource); + + + +MODx.window.QuickCreateResource = function(config) { + config = config || {}; + this.ident = config.ident || 'qcr'+Ext.id(); + Ext.applyIf(config,{ + title: _('quick_create_resource') + ,id: this.ident + ,bwrapCssClass: 'x-window-with-tabs' + ,width: 700 + //,height: ['modSymLink', 'modWebLink', 'modStaticResource'].indexOf(config.record.class_key) == -1 ? 640 : 498 + // ,autoHeight: false + ,layout: 'anchor' + ,url: MODx.config.connector_url + ,action: 'resource/create' + // ,shadow: false + ,fields: [{ + xtype: 'modx-tabs' + ,bodyStyle: { background: 'transparent' } + ,border: true + ,deferredRender: false + ,autoHeight: false + ,autoScroll: false + ,anchor: '100% 100%' + ,items: [{ + title: _('resource') + ,layout: 'form' + ,cls: 'modx-panel' + // ,bodyStyle: { background: 'transparent', padding: '10px' } // we handle this in CSS + ,autoHeight: false + ,anchor: '100% 100%' + ,labelWidth: 100 + ,items: [{ + xtype: 'hidden' + ,name: 'id' + },{ + layout: 'column' + ,border: false + ,items: [{ + columnWidth: .6 + ,border: false + ,layout: 'form' + ,items: [{ + xtype: 'textfield' + ,name: 'pagetitle' + ,id: 'modx-'+this.ident+'-pagetitle' + ,fieldLabel: _('resource_pagetitle')+'*' + ,description: '[[*pagetitle]]
'+_('resource_pagetitle_help') + ,anchor: '100%' + ,allowBlank: false + },{ + xtype: 'textfield' + ,name: 'longtitle' + ,id: 'modx-'+this.ident+'-longtitle' + ,fieldLabel: _('resource_longtitle') + ,description: '[[*longtitle]]
'+_('resource_longtitle_help') + ,anchor: '100%' + },{ + xtype: 'textarea' + ,name: 'description' + ,id: 'modx-'+this.ident+'-description' + ,fieldLabel: _('resource_description') + ,description: '[[*description]]
'+_('resource_description_help') + ,anchor: '100%' + ,grow: false + ,height: 50 + },{ + xtype: 'textarea' + ,name: 'introtext' + ,id: 'modx-'+this.ident+'-introtext' + ,fieldLabel: _('resource_summary') + ,description: '[[*introtext]]
'+_('resource_summary_help') + ,anchor: '100%' + ,height: 50 + }] + },{ + columnWidth: .4 + ,border: false + ,layout: 'form' + ,items: [{ + xtype: 'modx-combo-template' + ,name: 'template' + ,id: 'modx-'+this.ident+'-template' + ,fieldLabel: _('resource_template') + ,description: '[[*template]]
'+_('resource_template_help') + ,editable: false + ,anchor: '100%' + ,baseParams: { + action: 'element/template/getList' + ,combo: '1' + ,limit: 0 + } + ,value: MODx.config.default_template + },{ + xtype: 'textfield' + ,name: 'alias' + ,id: 'modx-'+this.ident+'-alias' + ,fieldLabel: _('resource_alias') + ,description: '[[*alias]]
'+_('resource_alias_help') + ,anchor: '100%' + },{ + xtype: 'textfield' + ,name: 'menutitle' + ,id: 'modx-'+this.ident+'-menutitle' + ,fieldLabel: _('resource_menutitle') + ,description: '[[*menutitle]]
'+_('resource_menutitle_help') + ,anchor: '100%' + },{ + xtype: 'textfield' + ,fieldLabel: _('resource_link_attributes') + ,description: '[[*link_attributes]]
'+_('resource_link_attributes_help') + ,name: 'link_attributes' + ,id: 'modx-'+this.ident+'-attributes' + ,maxLength: 255 + ,anchor: '100%' + },{ + xtype: 'xcheckbox' + ,boxLabel: _('resource_hide_from_menus') + ,description: '[[*hidemenu]]
'+_('resource_hide_from_menus_help') + ,hideLabel: true + ,name: 'hidemenu' + ,id: 'modx-'+this.ident+'-hidemenu' + ,inputValue: 1 + ,checked: MODx.config.hidemenu_default == '1' ? 1 : 0 + },{ + xtype: 'xcheckbox' + ,name: 'published' + ,id: 'modx-'+this.ident+'-published' + ,boxLabel: _('resource_published') + ,description: '[[*published]]
'+_('resource_published_help') + ,hideLabel: true + ,inputValue: 1 + ,checked: MODx.config.publish_default == '1' ? 1 : 0 + }] + }] + },MODx.getQRContentField(this.ident,config.record.class_key)] + },{ + id: 'modx-'+this.ident+'-settings' + ,title: _('settings') + ,layout: 'form' + ,cls: 'modx-panel' + ,autoHeight: true + ,forceLayout: true + ,labelWidth: 100 + ,defaults: { + autoHeight: true + ,border: false + } + // ,bodyStyle: { padding: '10px' } // we handle this in CSS + ,items: MODx.getQRSettings(this.ident,config.record) + }] + }] + ,keys: [{ + key: Ext.EventObject.ENTER + ,shift: true + ,fn: this.submit + ,scope: this + }] + }); + MODx.window.QuickCreateResource.superclass.constructor.call(this,config); +}; +Ext.extend(MODx.window.QuickCreateResource,MODx.Window); +Ext.reg('modx-window-quick-create-modResource',MODx.window.QuickCreateResource); + +MODx.window.QuickUpdateResource = function(config) { + config = config || {}; + this.ident = config.ident || 'qur'+Ext.id(); + Ext.applyIf(config,{ + title: _('quick_update_resource') + ,id: this.ident + ,action: 'resource/update' + ,buttons: [{ + text: config.cancelBtnText || _('cancel') + ,scope: this + ,handler: function() { this.hide(); } + },{ + text: config.saveBtnText || _('save') + ,scope: this + ,handler: function() { this.submit(false); } + },{ + text: config.saveBtnText || _('save_and_close') + ,cls: 'primary-button' + ,scope: this + ,handler: this.submit + }] + }); + MODx.window.QuickUpdateResource.superclass.constructor.call(this,config); +}; +Ext.extend(MODx.window.QuickUpdateResource,MODx.window.QuickCreateResource); +Ext.reg('modx-window-quick-update-modResource',MODx.window.QuickUpdateResource); + + +MODx.getQRContentField = function(id,cls) { + id = id || 'qur'; + cls = cls || 'modDocument'; + var dm = Ext.getBody().getViewSize(); + var o = {}; + switch (cls) { + case 'modSymLink': + o = { + xtype: 'textfield' + ,fieldLabel: _('symlink') + ,name: 'content' + ,id: 'modx-'+id+'-content' + ,anchor: '100%' + ,maxLength: 255 + ,allowBlank: false + }; + break; + case 'modWebLink': + o = { + xtype: 'textfield' + ,fieldLabel: _('weblink') + ,name: 'content' + ,id: 'modx-'+id+'-content' + ,anchor: '100%' + ,maxLength: 255 + ,value: 'http://' + ,allowBlank: false + }; + break; + case 'modStaticResource': + o = { + xtype: 'modx-combo-browser' + ,browserEl: 'modx-browser' + ,prependPath: false + ,prependUrl: false + // ,hideFiles: true + ,fieldLabel: _('static_resource') + ,name: 'content' + ,id: 'modx-'+id+'-content' + ,anchor: '100%' + ,maxLength: 255 + ,value: '' + ,listeners: { + 'select':{fn:function(data) { + if (data.url.substring(0,1) == '/') { + Ext.getCmp('modx-'+id+'-content').setValue(data.url.substring(1)); + } + },scope:this} + } + }; + break; + case 'modResource': + case 'modDocument': + default: + o = { + xtype: 'textarea' + ,name: 'content' + ,id: 'modx-'+id+'-content' + // ,hideLabel: true + ,fieldLabel: _('content') + ,labelSeparator: '' + ,anchor: '100%' + ,style: 'min-height: 200px' + ,grow: true + }; + break; + } + return o; +}; + +MODx.getQRSettings = function(id,va) { + id = id || 'qur'; + return [{ + layout: 'column' + ,border: false + ,anchor: '100%' + ,defaults: { + labelSeparator: '' + ,labelAlign: 'top' + ,border: false + ,layout: 'form' + } + ,items: [{ + columnWidth: .5 + ,items: [{ + xtype: 'hidden' + ,name: 'parent' + ,id: 'modx-'+id+'-parent' + ,value: va['parent'] + },{ + xtype: 'hidden' + ,name: 'context_key' + ,id: 'modx-'+id+'-context_key' + ,value: va['context_key'] + },{ + xtype: 'hidden' + ,name: 'class_key' + ,id: 'modx-'+id+'-class_key' + ,value: va['class_key'] + },{ + xtype: 'hidden' + ,name: 'publishedon' + ,id: 'modx-'+id+'-publishedon' + ,value: va['publishedon'] + },{ + xtype: 'modx-field-parent-change' + ,fieldLabel: _('resource_parent') + ,description: '[[*parent]]
'+_('resource_parent_help') + ,name: 'parent-cmb' + ,id: 'modx-'+id+'-parent-change' + ,value: va['parent'] || 0 + ,anchor: '100%' + ,parentcmp: 'modx-'+id+'-parent' + ,contextcmp: 'modx-'+id+'-context_key' + ,currentid: va['id'] + },{ + xtype: 'modx-combo-class-derivatives' + ,fieldLabel: _('resource_type') + ,description: '[[*class_key]]
' + ,name: 'class_key' + ,hiddenName: 'class_key' + ,id: 'modx-'+id+'-class-key' + ,anchor: '100%' + ,value: va['class_key'] != undefined ? va['class_key'] : 'modDocument' + },{ + xtype: 'modx-combo-content-type' + ,fieldLabel: _('resource_content_type') + ,description: '[[*content_type]]
'+_('resource_content_type_help') + ,name: 'content_type' + ,hiddenName: 'content_type' + ,id: 'modx-'+id+'-type' + ,anchor: '100%' + ,value: va['content_type'] != undefined ? va['content_type'] : (MODx.config.default_content_type || 1) + + },{ + xtype: 'modx-combo-content-disposition' + ,fieldLabel: _('resource_contentdispo') + ,description: '[[*content_dispo]]
'+_('resource_contentdispo_help') + ,name: 'content_dispo' + ,hiddenName: 'content_dispo' + ,id: 'modx-'+id+'-dispo' + ,anchor: '100%' + ,value: va['content_dispo'] != undefined ? va['content_dispo'] : 0 + },{ + xtype: 'numberfield' + ,fieldLabel: _('resource_menuindex') + ,description: '[[*menuindex]]
'+_('resource_menuindex_help') + ,name: 'menuindex' + ,id: 'modx-'+id+'-menuindex' + ,width: 75 + ,value: va['menuindex'] || 0 + }] + },{ + columnWidth: .5 + ,items: [{ + xtype: 'xdatetime' + ,fieldLabel: _('resource_publishedon') + ,description: '[[*publishedon]]
'+_('resource_publishedon_help') + ,name: 'publishedon' + ,id: 'modx-'+id+'-publishedon' + ,allowBlank: true + ,dateFormat: MODx.config.manager_date_format + ,timeFormat: MODx.config.manager_time_format + ,startDay: parseInt(MODx.config.manager_week_start) + ,dateWidth: 153 + ,timeWidth: 153 + ,offset_time: MODx.config.server_offset_time + ,value: va['publishedon'] + },{ + xtype: va['canpublish'] ? 'xdatetime' : 'hidden' + ,fieldLabel: _('resource_publishdate') + ,description: '[[*pub_date]]
'+_('resource_publishdate_help') + ,name: 'pub_date' + ,id: 'modx-'+id+'-pub-date' + ,allowBlank: true + ,dateFormat: MODx.config.manager_date_format + ,timeFormat: MODx.config.manager_time_format + ,startDay: parseInt(MODx.config.manager_week_start) + ,dateWidth: 153 + ,timeWidth: 153 + ,offset_time: MODx.config.server_offset_time + ,value: va['pub_date'] + },{ + xtype: va['canpublish'] ? 'xdatetime' : 'hidden' + ,fieldLabel: _('resource_unpublishdate') + ,description: '[[*unpub_date]]
'+_('resource_unpublishdate_help') + ,name: 'unpub_date' + ,id: 'modx-'+id+'-unpub-date' + ,allowBlank: true + ,dateFormat: MODx.config.manager_date_format + ,timeFormat: MODx.config.manager_time_format + ,startDay: parseInt(MODx.config.manager_week_start) + ,dateWidth: 153 + ,timeWidth: 153 + ,offset_time: MODx.config.server_offset_time + ,value: va['unpub_date'] + },{ + xtype: 'xcheckbox' + ,boxLabel: _('resource_folder') + ,description: _('resource_folder_help') + ,hideLabel: true + ,name: 'isfolder' + ,id: 'modx-'+id+'-isfolder' + ,inputValue: 1 + ,checked: va['isfolder'] != undefined ? va['isfolder'] : false + },{ + xtype: 'xcheckbox' + ,boxLabel: _('resource_richtext') + ,description: _('resource_richtext_help') + ,hideLabel: true + ,name: 'richtext' + ,id: 'modx-'+id+'-richtext' + ,inputValue: 1 + ,checked: va['richtext'] !== undefined ? (va['richtext'] ? 1 : 0) : (MODx.config.richtext_default == '1' ? 1 : 0) + },{ + xtype: 'xcheckbox' + ,boxLabel: _('resource_searchable') + ,description: _('resource_searchable_help') + ,hideLabel: true + ,name: 'searchable' + ,id: 'modx-'+id+'-searchable' + ,inputValue: 1 + ,checked: va['searchable'] != undefined ? va['searchable'] : (MODx.config.search_default == '1' ? 1 : 0) + ,listeners: {'check': {fn:MODx.handleQUCB}} + },{ + xtype: 'xcheckbox' + ,boxLabel: _('resource_cacheable') + ,description: _('resource_cacheable_help') + ,hideLabel: true + ,name: 'cacheable' + ,id: 'modx-'+id+'-cacheable' + ,inputValue: 1 + ,checked: va['cacheable'] != undefined ? va['cacheable'] : (MODx.config.cache_default == '1' ? 1 : 0) + },{ + xtype: 'xcheckbox' + ,name: 'clearCache' + ,id: 'modx-'+id+'-clearcache' + ,boxLabel: _('clear_cache_on_save') + ,description: _('clear_cache_on_save_msg') + ,hideLabel: true + ,inputValue: 1 + ,checked: true + },{ + xtype: 'xcheckbox' + ,boxLabel: _('deleted') + ,description: _('resource_delete') + ,hideLabel: true + ,name: 'deleted' + ,id: 'modx-'+id+'-deleted' + ,inputValue: 1 + ,checked: va['deleted'] != undefined ? va['deleted'] : 0 + },{ + xtype: 'xcheckbox' + ,boxLabel: _('resource_alias_visible') + ,description: _('resource_alias_visible_help') + ,hideLabel: true + ,name: 'alias_visible' + ,id: 'modx-'+id+'-alias-visible' + ,inputValue: 1 + ,checked: va['alias_visible'] != undefined ? va['alias_visible'] : 1 + },{ + xtype: 'xcheckbox' + ,boxLabel: _('resource_uri_override') + ,description: _('resource_uri_override_help') + ,hideLabel: true + ,name: 'uri_override' + ,id: 'modx-'+id+'-uri-override' + ,value: 1 + ,checked: parseInt(va['uri_override']) ? true : false + ,listeners: {'check': {fn:MODx.handleFreezeUri}} + },{ + xtype: 'textfield' + ,fieldLabel: _('resource_uri') + ,description: '[[*uri]]
'+_('resource_uri_help') + ,name: 'uri' + ,id: 'modx-'+id+'-uri' + ,maxLength: 255 + ,anchor: '100%' + ,value: va['uri'] || '' + ,hidden: !va['uri_override'] + }] + }] + }]; +}; +MODx.handleQUCB = function(cb) { + var h = Ext.getCmp(cb.id+'-hd'); + if (cb.checked && h) { + cb.setValue(1); + h.setValue(1); + } else if (h) { + cb.setValue(0); + h.setValue(0); + } +}; + +MODx.handleFreezeUri = function(cb) { + var uri = Ext.getCmp(cb.id.replace('-override', '')); + if (!uri) { return false; } + if (cb.checked) { + uri.show(); + } else { + uri.hide(); + } +}; + +Ext.override(Ext.tree.AsyncTreeNode,{ + + listeners: { + click: {fn: function(){ + console.log('Clicked me!',arguments); + return false; + },scope: this} + } +}); diff --git a/manager/controllers/default/resource/trash/index.class.php b/manager/controllers/default/resource/trash/index.class.php new file mode 100644 index 00000000000..8a5f2905295 --- /dev/null +++ b/manager/controllers/default/resource/trash/index.class.php @@ -0,0 +1,39 @@ +modx->getOption('manager_url',null,MODX_MANAGER_URL); + $this->addJavascript($mgrUrl.'assets/modext/widgets/resource/modx.panel.trash.js'); + $this->addJavascript($mgrUrl.'assets/modext/sections/resource/trash/index.js'); + $this->addHtml(''); + } + + /** + * @return null|string + */ + public function getPageTitle() { + return $this->modx->lexicon('trash.page_title'); + } + + + /** + * Check for any permissions or requirements to load page + * @return bool + */ + public function checkPermissions() { + return $this->modx->hasPermission('menu_trash'); + } + + + /** + * Specify the language topics to load + * @return array + */ + public function getLanguageTopics() { + return array('trash','namespace'); + } +} \ No newline at end of file diff --git a/manager/templates/default/resource/trash/index.tpl b/manager/templates/default/resource/trash/index.tpl new file mode 100644 index 00000000000..178f6dc44d2 --- /dev/null +++ b/manager/templates/default/resource/trash/index.tpl @@ -0,0 +1,34 @@ + + +
+
+

[[%resource_trash_title]]

+ +
+
Some description
+
+
+ + + + + + +
+
+
+
+ + +
+ + + + + +
+
\ No newline at end of file