0012950: More attachment methods for mail
authorMichael Spahn <m.spahn@metaways.de>
Wed, 19 Apr 2017 14:54:12 +0000 (16:54 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 15 Jun 2017 11:39:00 +0000 (13:39 +0200)
allows to set mail attachments in multiple ways:
* use uploaded file
* use uploaded file (as download link)
* use file from Filemanager
* use file from Filemanager (as download link)
* set password protection for download links

!usermanual

https://forge.tine20.org/view.php?id=12950

Change-Id: Id585edf70a05e36d2a468b69151128a808a91565
Reviewed-on: http://gerrit.tine20.com/customers/4558
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Felamimail/Frontend/JsonTest.php
tine20/Felamimail/Controller/Message/Send.php
tine20/Felamimail/Felamimail.jsb2
tine20/Felamimail/js/AttachmentUploadGrid.js [new file with mode: 0644]
tine20/Felamimail/js/MessageEditDialog.js
tine20/Filemanager/Controller/Node.php
tine20/Filemanager/js/FilePicker.js
tine20/Tinebase/Frontend/Http.php
tine20/Tinebase/Model/TempFile.php
tine20/Tinebase/js/widgets/grid/FileUploadGrid.js

index 00e6ffc..b907ad9 100644 (file)
@@ -1072,10 +1072,10 @@ class Felamimail_Frontend_JsonTest extends TestCase
         $subject = 'attachment test';
         $messageToSend = $this->_getMessageData('unittestalias@' . $this->_mailDomain, $subject);
         $tempfileName = 'jsontest' . Tinebase_Record_Abstract::generateUID(10);
         $subject = 'attachment test';
         $messageToSend = $this->_getMessageData('unittestalias@' . $this->_mailDomain, $subject);
         $tempfileName = 'jsontest' . Tinebase_Record_Abstract::generateUID(10);
-        $tempfilePath = Tinebase_Core::getTempDir() . DIRECTORY_SEPARATOR . $tempfileName;
-        file_put_contents($tempfilePath, 'some content');
-        $tempFile = Tinebase_TempFile::getInstance()->createTempFile($tempfilePath, $tempfileName);
-        $messageToSend['attachments'] = array(array('tempFile' => array('id' => $tempFile->getId())));
+        $tempFile = $this->_createTempFile($tempfileName);
+        $messageToSend['attachments'] = array(
+            array('tempFile' => array('id' => $tempFile->getId(), 'type' => $tempFile->type))
+        );
         $this->_json->saveMessage($messageToSend);
         $forwardMessage = $this->_searchForMessageBySubject($subject);
         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
         $this->_json->saveMessage($messageToSend);
         $forwardMessage = $this->_searchForMessageBySubject($subject);
         $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
@@ -2101,4 +2101,183 @@ IbVx8ZTO7dJRKrg72aFmWTf0uNla7vicAhpiLWobyNYcZbIjrAGDfg==
         $this->assertTrue(isset($complete['headers']['reply-to']), print_r($complete, true));
         $this->assertEquals('"Tine 2.0 Admin Account" <noreply@tine20.org>', $complete['headers']['reply-to']);
     }
         $this->assertTrue(isset($complete['headers']['reply-to']), print_r($complete, true));
         $this->assertEquals('"Tine 2.0 Admin Account" <noreply@tine20.org>', $complete['headers']['reply-to']);
     }
+
+    /**
+     * Its possible to choice the kind of attachment when adding it.
+     *
+     * type = tempfile: uploaded from harddisk, supposed to be a regular attachment
+     *
+     * @see 0012950: More attachment methods for mail
+     */
+    public function testAttachmentMethodAttachment()
+    {
+        $message = $this->_testAttachmentType('tempfile');
+
+        self::assertTrue(isset($message['attachments']), 'no attachment set: ' . print_r($message, true));
+        self::assertEquals(1, count($message['attachments']), 'no attachment set: ' . print_r($message, true));
+        self::assertEquals('foobar1.txt', $message['attachments'][0]['filename']);
+        self::assertEquals(16, $message['attachments'][0]['size']);
+    }
+
+    /**
+     * Its possible to choice the kind of attachment when adding it.
+     *
+     * type = download_public: uploaded from harddisk, supposed to be a public download link
+     *
+     * @see 0012950: More attachment methods for mail
+     */
+    public function testAttachmentMethodPublicDownloadLinkUpload()
+    {
+        $message = $this->_testAttachmentType('download_public');
+
+        self::assertTrue(isset($message['attachments']), 'attachment set: ' . print_r($message, true));
+        self::assertEquals(0, count($message['attachments']), 'attachment set: ' . print_r($message, true));
+        self::assertContains('/download', $message['body'], 'no download link in body: ' . print_r($message, true));
+    }
+
+    /**
+     * Its possible to choice the kind of attachment when adding it.
+     *
+     * type = download_public_fm: chosen from fm, supposed to be a public download link
+     *
+     * @see 0012950: More attachment methods for mail
+     */
+    public function testAttachmentMethodPublicDownloadLinkFromFilemanager()
+    {
+        $message = $this->_testAttachmentType('download_public_fm');
+
+        self::assertTrue(isset($message['attachments']), 'attachment set: ' . print_r($message, true));
+        self::assertEquals(0, count($message['attachments']), 'attachment set: ' . print_r($message, true));
+        self::assertContains('/download', $message['body'], 'no download link in body: ' . print_r($message, true));
+    }
+
+    /**
+     * Its possible to choice the kind of attachment when adding it.
+     *
+     * type = download_protected: uploaded from harddisk, supposed to be a protected download link
+     *
+     * @see 0012950: More attachment methods for mail
+     */
+    public function testAttachmentMethodProtectedDownloadLink()
+    {
+        $message = $this->_testAttachmentType('download_protected');
+
+        self::assertTrue(isset($message['attachments']), 'attachment set: ' . print_r($message, true));
+        self::assertEquals(0, count($message['attachments']), 'attachment set: ' . print_r($message, true));
+        self::assertContains('/download', $message['body'], 'no download link in body: ' . print_r($message, true));
+
+        // download link id is at the end of message body
+        $downloadLinkId = trim(substr($message['body'], -46, 40));
+        $dl = Filemanager_Controller_DownloadLink::getInstance()->get($downloadLinkId);
+        self::assertTrue(Filemanager_Controller_DownloadLink::getInstance()->validatePassword($dl, 'test'));
+    }
+
+    /**
+     * Its possible to choice the kind of attachment when adding it.
+     *
+     * type = filenode: chosen from fm, thats why type -> file, but the filemanager file is supposed to be used as a regular attachment
+     *
+     * @see 0012950: More attachment methods for mail
+     */
+    public function testAttachmentMethodFilemanagerNode()
+    {
+        $message = $this->_testAttachmentType('filenode');
+
+        self::assertTrue(isset($message['attachments']), 'no attachment set: ' . print_r($message, true));
+        self::assertEquals(1, count($message['attachments']), 'no attachment set: ' . print_r($message, true));
+        self::assertEquals('test.txt', $message['attachments'][0]['filename']);
+        self::assertEquals(16, $message['attachments'][0]['size']);
+    }
+
+    /**
+     * @param $type
+     * @return array
+     *
+     * @throws Filemanager_Exception_NodeExists
+     * @throws Tinebase_Exception_InvalidArgument
+     */
+    protected function _testAttachmentType($type)
+    {
+        $this->_foldersToClear = array('INBOX', $this->_account->sent_folder);
+        $tempfile = $this->_createTempFile('foobar1.txt');
+
+        if (in_array($type, array('tempfile', 'download_public', 'download_protected'))) {
+            // attach uploaded tempfile
+            $attachment = array(
+                'tempFile' => $tempfile->toArray(),
+                'name' => 'foobar1.txt',
+                'size' => $tempfile->size,
+                'type' => 'text/plain',
+                'id' => 'eeabe57fd3712a9fe27a34df07cb44cab9e9afb3',
+                'attachment_type' => $type,
+            );
+
+        } elseif (in_array($type, array('filenode', 'download_public_fm', 'download_protected_fm'))) {
+            // attach existing file from filemanager
+            $nodeController = Filemanager_Controller_Node::getInstance();
+            $testPath = '/' . Tinebase_FileSystem::FOLDER_TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName . '/testcontainer';
+            $result = $nodeController->createNodes($testPath, Tinebase_Model_Tree_FileObject::TYPE_FOLDER, array(), FALSE);
+            $personalNode = $result[0];
+            $filepath = $personalNode->path . '/test.txt';
+
+            // create empty file first (like the js frontend does)
+            $nodeController->createNodes($filepath, Tinebase_Model_Tree_FileObject::TYPE_FILE, array(), FALSE);
+            $fmFile = $nodeController->createNodes(
+                $filepath,
+                Tinebase_Model_Tree_FileObject::TYPE_FILE,
+                (array)$tempfile->getId(), TRUE
+            )->getFirstRecord();
+
+            $attachment = [ // chosen from fm, thats why type -> file, but the filemanager file is supposed to be used as a regular attachment
+                'name' => $fmFile->name,
+                'path' => $fmFile->path,
+                'size' => $fmFile->size,
+                'type' => $fmFile->contenttype,
+                'id' => $fmFile->getId(),
+                'attachment_type' => $type,
+            ];
+
+        } else {
+            throw new Tinebase_Exception_InvalidArgument('invalid type given');
+        }
+
+        if (in_array($type, array('download_protected_fm', 'download_protected'))) {
+            $attachment['password'] = 'test';
+        }
+
+        $messageToSend = [
+            'note' => null,
+            'content_type' => 'text/html',
+            'account_id' => $this->_account->id,
+            'to' => [
+                $this->_account->email
+            ],
+            'cc' => [],
+            'bcc' => [],
+            'subject' => 'attachment test [' . $type . ']',
+            'body' => 'foobar',
+
+            'attachments' => [ $attachment ],
+            'from_email' => 'vagrant@example.org',
+            'customfields' => [],
+            'headers' => array('X-Tine20TestMessage' => 'jsontest'),
+        ];
+
+        $this->_json->saveMessage($messageToSend);
+        $message = $this->_searchForMessageBySubject($messageToSend['subject']);
+        $complete = $this->_json->getMessage($message['id']);
+
+        return $complete;
+    }
+
+    /**
+     * @param string $tempfileName
+     * @return Tinebase_Model_TempFile
+     */
+    protected function _createTempFile($tempfileName = 'test.txt')
+    {
+        $tempfilePath = Tinebase_Core::getTempDir() . DIRECTORY_SEPARATOR . $tempfileName;
+        file_put_contents($tempfilePath, 'some content');
+        return Tinebase_TempFile::getInstance()->createTempFile($tempfilePath, $tempfileName);
+    }
 }
 }
index 5592764..22ebea0 100644 (file)
@@ -73,6 +73,7 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
         $oldMaxExcecutionTime = Tinebase_Core::setExecutionLifeTime(300); // 5 minutes
         
         $account = Felamimail_Controller_Account::getInstance()->get($_message->account_id);
         $oldMaxExcecutionTime = Tinebase_Core::setExecutionLifeTime(300); // 5 minutes
         
         $account = Felamimail_Controller_Account::getInstance()->get($_message->account_id);
+        
         try {
             $this->_resolveOriginalMessage($_message);
             $mail = $this->createMailForSending($_message, $account, $nonPrivateRecipients);
         try {
             $this->_resolveOriginalMessage($_message);
             $mail = $this->createMailForSending($_message, $account, $nonPrivateRecipients);
@@ -200,13 +201,13 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
         $mail = new Tinebase_Mail('UTF-8');
         $mail->setSubject($_message->subject);
         
         $mail = new Tinebase_Mail('UTF-8');
         $mail->setSubject($_message->subject);
         
-        $this->_setMailBody($mail, $_message);
         $this->_setMailFrom($mail, $_account, $_message);
         $_nonPrivateRecipients = $this->_setMailRecipients($mail, $_message);
         $this->_setMailFrom($mail, $_account, $_message);
         $_nonPrivateRecipients = $this->_setMailRecipients($mail, $_message);
+
         $this->_setMailHeaders($mail, $_account, $_message);
         $this->_setMailHeaders($mail, $_account, $_message);
-        
         $this->_addAttachments($mail, $_message);
         $this->_addAttachments($mail, $_message);
-        
+        $this->_setMailBody($mail, $_message);
+
         return $mail;
     }
     
         return $mail;
     }
     
@@ -625,33 +626,17 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
         $totalSize = 0;
 
         foreach ($_message->attachments as $attachment) {
         $totalSize = 0;
 
         foreach ($_message->attachments as $attachment) {
-            if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
-                . ' Adding attachment: ' . (is_object($attachment) ? print_r($attachment->toArray(), TRUE) : print_r($attachment, TRUE)));
-
-            if (isset($attachment['type'])
-                && $attachment['type'] == Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822
-                && $_message->original_id instanceof Felamimail_Model_Message
-            ) {
-                $part = $this->_getRfc822Attachment($attachment, $_message);
-
-            } else if (isset($attachment['type'])
-                && $attachment['type'] == 'filenode'
-            ) {
-                $part = $this->_getFileNodeAttachment($attachment);
-
-            } else if ($attachment instanceof Tinebase_Model_TempFile || isset($attachment['tempFile'])) {
-                $part = $this->_getTempFileAttachment($attachment);
+            $part = $this->_getAttachmentPartByType($attachment, $_message);
 
 
-            } else {
-                $part = $this->_getMessagePartAttachment($attachment);
-            }
-
-            if (! $part || empty($attachment['type'])) {
+            if (! $part || ! isset($attachment['type'])) {
                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                     . ' Skipping attachment ' . print_r($attachment, true));
                 continue;
             }
                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                     . ' Skipping attachment ' . print_r($attachment, true));
                 continue;
             }
-            
+
+            if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
+                . ' Adding attachment: ' . (is_object($attachment) ? print_r($attachment->toArray(), TRUE) : print_r($attachment, TRUE)));
+
             $part->setTypeAndDispositionForAttachment($attachment['type'], $attachment['name']);
 
             if (! empty($attachment['size'])) {
             $part->setTypeAndDispositionForAttachment($attachment['type'], $attachment['name']);
 
             if (! empty($attachment['size'])) {
@@ -673,6 +658,71 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
     }
 
     /**
     }
 
     /**
+     * @param $attachment
+     * @return null|Zend_Mime_Part
+     */
+    protected function _getAttachmentPartByType(&$attachment, $_message)
+    {
+        $part = null;
+
+        $attachmentType = $this->_getAttachmentType($attachment, $_message);
+
+        switch ($attachmentType) {
+            case 'rfc822':
+                $part = $this->_getRfc822Attachment($attachment, $_message);
+                break;
+            case 'systemlink_fm':
+                $this->_setSystemlinkAttachment($attachment, $_message);
+                break;
+            case 'download_public':
+            case 'download_public_fm':
+                // no attachment part
+                $this->_setDownloadLinkAttachment($attachment, $_message);
+                break;
+            case 'download_protected':
+            case 'download_protected_fm':
+                // no attachment part
+                $this->_setDownloadLinkAttachment($attachment, $_message, /* protected */ true);
+                break;
+            case 'filenode':
+                $part = $this->_getFileNodeAttachment($attachment);
+                break;
+            case 'tempfile':
+                $part = $this->_getTempFileAttachment($attachment);
+                break;
+            default:
+                $part = $this->_getMessagePartAttachment($attachment);
+        }
+
+        return $part;
+    }
+
+    protected function _getAttachmentType($attachment, $_message)
+    {
+        // Determine if it's a tempfile attachment or a filenode attachment
+        if (isset($attachment['attachment_type']) && $attachment['attachment_type'] === 'attachment' && $attachment['tempFile']) {
+            $attachment['attachment_type'] = 'tempfile';
+        }
+
+        if (isset($attachment['attachment_type']) && $attachment['attachment_type'] === 'attachment' && !$attachment['tempFile']) {
+            $attachment['attachment_type'] = 'filenode';
+        }
+
+        if (isset($attachment['attachment_type'])) {
+            return $attachment['attachment_type'];
+        } elseif (isset($attachment['type'])
+            && $attachment['type'] === Felamimail_Model_Message::CONTENT_TYPE_MESSAGE_RFC822
+            && $_message->original_id instanceof Felamimail_Model_Message
+        ) {
+            return 'rfc822';
+        } elseif ($attachment instanceof Tinebase_Model_TempFile || isset($attachment['tempFile'])) {
+            return 'tempfile';
+        }
+
+        return null;
+    }
+
+    /**
      * get attachment of type CONTENT_TYPE_MESSAGE_RFC822
      *
      * @param $attachment
      * get attachment of type CONTENT_TYPE_MESSAGE_RFC822
      *
      * @param $attachment
@@ -691,21 +741,117 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
     }
 
     /**
     }
 
     /**
+     * @param            $_attachment
+     * @param            $_message
+     * @param bool|false $_protected
+     * @return boolean success
+     */
+    protected function _setDownloadLinkAttachment($_attachment, $_message, $_protected = false)
+    {
+        if (! Tinebase_Core::getUser()->hasRight('Filemanager', Tinebase_Acl_Rights::RUN)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
+                . ' No right to run Filemanager');
+            return false;
+        }
+
+        $password = $_protected && isset($_attachment['password']) ? $_attachment['password'] : '';
+        $tempFile = $this->_getTempFileFromAttachment($_attachment);
+        if ($tempFile) {
+            $translate = Tinebase_Translation::getTranslation('Felamimail');
+            $downloadLinkFolder = '/' . Tinebase_FileSystem::FOLDER_TYPE_PERSONAL
+                . '/' . Tinebase_Core::getUser()->getId()
+                . '/' . $translate->_('.My Mail Download Links');
+            $downloadLink = Filemanager_Controller_Node::getInstance()->createNodeWithDownloadLinkFromTempFile(
+                $tempFile,
+                $downloadLinkFolder,
+                $password
+            );
+        } else {
+            $node = Filemanager_Controller_Node::getInstance()->get($_attachment['id']);
+
+            if (!Tinebase_Core::getUser()->hasGrant($node, Tinebase_Model_Grants::GRANT_PUBLISH)) {
+                return false;
+            }
+
+            $downloadLink = Filemanager_Controller_DownloadLink::getInstance()->create(new Filemanager_Model_DownloadLink(array(
+                'node_id'       => $node->getId(),
+                'expiry_date'   => Tinebase_DateTime::now()->addDay(30)->toString(),
+                'password'      => $password
+            )));
+        }
+
+        $this->_insertDownloadLinkIntoMailBody($downloadLink->url, $_message);
+
+        return true;
+    }
+
+    /**
+     * @param $_attachment
+     * @param $_message
+     * @return bool
+     */
+    protected function _setSystemlinkAttachment($_attachment, $_message)
+    {
+        if (! Tinebase_Core::getUser()->hasRight('Filemanager', Tinebase_Acl_Rights::RUN)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
+                . ' No right to run Filemanager');
+            return false;
+        }
+
+        $node = Filemanager_Controller_Node::getInstance()->get($_attachment['id']);
+
+        $this->_insertDownloadLinkIntoMailBody(Filemanager_Model_node::getDeepLink($node), $_message);
+
+        return true;
+    }
+
+    /**
+     * @param $_link
+     * @param $_message
+     *
+     * TODO insert above signature
+     */
+    protected function _insertDownloadLinkIntoMailBody($_link, $_message)
+    {
+        if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
+            Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+                . ' Inserting download link into mail body: ' . $_link);
+        }
+
+        if ('text/html' === $_message->content_type) {
+            $_message->body .= sprintf(
+                '<br />%s<br />',
+                $_link
+            );
+        } else {
+            $_message->body .= "\n" . $_link . "\n";
+        }
+    }
+
+    /**
      * get attachment defined by a file node (mailfiler or filemanager)
      *
      * @param $attachment
      * @return null|Zend_Mime_Part
      * @throws Tinebase_Exception_NotFound
      *
      * get attachment defined by a file node (mailfiler or filemanager)
      *
      * @param $attachment
      * @return null|Zend_Mime_Part
      * @throws Tinebase_Exception_NotFound
      *
-     * TODO support Filemanager files
-     * TODO allow to omit $messageuid, $partId
-     * TODO write a test for this
      */
     protected function _getFileNodeAttachment(&$attachment)
     {
      */
     protected function _getFileNodeAttachment(&$attachment)
     {
-        list($appname, $path, $messageuid, $partId) = explode('|', $attachment['id']);
+        if (isset($attachment['path'])) {
+            // allow Filemanager?
+            $appname = 'Filemanager';
+            $path = $attachment['path'];
+        } else {
+            list($appname, $path, $messageuid, $partId) = explode('|', $attachment['id']);
+        }
 
 
-        $nodeController = Tinebase_Core::getApplicationInstance($appname . '_Model_Node');
+        try {
+            $nodeController = Tinebase_Core::getApplicationInstance($appname . '_Model_Node');
+        } catch (Tinebase_Exception $te) {
+            Tinebase_Exception::log($te);
+            return null;
+        }
 
         // remove filename from path
         // TODO remove DRY with \MailFiler_Frontend_Http::downloadAttachment
 
         // remove filename from path
         // TODO remove DRY with \MailFiler_Frontend_Http::downloadAttachment
@@ -713,31 +859,50 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
         array_pop($pathParts);
         $path = implode('/', $pathParts);
 
         array_pop($pathParts);
         $path = implode('/', $pathParts);
 
-        $filter = array(
-            array(
-                'field'    => 'path',
-                'operator' => 'equals',
-                'value'    => $path
-            ),
-            array(
-                'field'    => 'messageuid',
-                'operator' => 'equals',
-                'value'    => $messageuid
-            ));
-        $node = $nodeController->search(new MailFiler_Model_NodeFilter($filter))->getFirstRecord();
+        if ($appname === 'MailFiler') {
+            $filter = array(
+                array(
+                    'field' => 'path',
+                    'operator' => 'equals',
+                    'value' => $path
+                ),
+                array(
+                    'field' => 'messageuid',
+                    'operator' => 'equals',
+                    'value' => $messageuid
+                )
+            );
+            $node = $nodeController->search(new MailFiler_Model_NodeFilter($filter))->getFirstRecord();
+        } else {
+            $nodeController = Filemanager_Controller_Node::getInstance();
+            $node = $nodeController->get($attachment['id']);
+
+            if (!Tinebase_Core::getUser()->hasGrant($node, Tinebase_Model_Grants::GRANT_DOWNLOAD)) {
+                return null;
+            }
+
+            $pathRecord = Tinebase_Model_Tree_Node_Path::createFromPath(
+                Filemanager_Controller_Node::getInstance()->addBasePath($node->path)
+            );
+        }
+
         if ($node) {
             if ($appname === 'MailFiler') {
                 $mailpart = MailFiler_Controller_Message::getInstance()->getPartFromNode($node, $partId);
         if ($node) {
             if ($appname === 'MailFiler') {
                 $mailpart = MailFiler_Controller_Message::getInstance()->getPartFromNode($node, $partId);
-
-                // TODO use streams
+                // TODO use stream
                 $content = Felamimail_Message::getDecodedContent($mailpart);
                 $content = Felamimail_Message::getDecodedContent($mailpart);
-                $part = new Zend_Mime_Part($content);
-                $part->encoding = Zend_Mime::ENCODING_BASE64;
+
+            } elseif ($appname === 'Filemanager') {
+                $content = fopen($pathRecord->streamwrapperpath, 'r');
 
             } else {
                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
                     . ' We don\'t support ' . $appname . ' nodes as attachment yet.');
             }
 
             } else {
                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
                     . ' We don\'t support ' . $appname . ' nodes as attachment yet.');
             }
+
+            $part = new Zend_Mime_Part($content);
+            $part->encoding = Zend_Mime::ENCODING_BASE64;
+
         } else {
             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
                 . ' Could not find file node attachment');
         } else {
             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
                 . ' Could not find file node attachment');
@@ -756,11 +921,7 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
      */
     protected function _getTempFileAttachment(&$attachment)
     {
      */
     protected function _getTempFileAttachment(&$attachment)
     {
-        $tempFileBackend = Tinebase_TempFile::getInstance();
-        $tempFile = ($attachment instanceof Tinebase_Model_TempFile)
-            ? $attachment
-            : (((isset($attachment['tempFile']) || array_key_exists('tempFile', $attachment))) ? $tempFileBackend->get($attachment['tempFile']['id']) : NULL);
-
+        $tempFile = $this->_getTempFileFromAttachment($attachment);
         if ($tempFile === null) {
             return null;
         }
         if ($tempFile === null) {
             return null;
         }
@@ -788,6 +949,21 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
     }
 
     /**
     }
 
     /**
+     * @param $attachment
+     * @return null|Tinebase_Model_TempFile|Tinebase_Record_Interface
+     * @throws Tinebase_Exception_NotFound
+     */
+    protected function _getTempFileFromAttachment($attachment)
+    {
+        $tempFileBackend = Tinebase_TempFile::getInstance();
+        $tempFile = ($attachment instanceof Tinebase_Model_TempFile)
+            ? $attachment
+            : (((isset($attachment['tempFile']) || array_key_exists('tempFile', $attachment))) ? $tempFileBackend->get($attachment['tempFile']['id']) : NULL);
+
+        return $tempFile;
+    }
+
+    /**
      * get attachment part defined by message id + part id
      *
      * @param $attachment
      * get attachment part defined by message id + part id
      *
      * @param $attachment
index b58fd68..58a98ac 100644 (file)
@@ -18,6 +18,9 @@
         {
           "text": "GridPanelHook.js",
           "path": "js/"
         {
           "text": "GridPanelHook.js",
           "path": "js/"
+        }, {
+          "text": "AttachmentUploadGrid.js",
+          "path": "js/"
         },
         {
           "text": "FolderStore.js",
         },
         {
           "text": "FolderStore.js",
diff --git a/tine20/Felamimail/js/AttachmentUploadGrid.js b/tine20/Felamimail/js/AttachmentUploadGrid.js
new file mode 100644 (file)
index 0000000..8648115
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * Tine 2.0
+ *
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <m.spahn@metaways.de>
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+Ext.ns('Tine.Felamimail');
+
+/**
+ * @namespace   Tine.Felamimail
+ * @class       Tine.Felamimail.AttachmentUploadGrid
+ * @extends     Ext.grid.GridPanel
+ *
+ * @author      Michael Spahn <m.spahn@metaways.de>
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ */
+Tine.Felamimail.AttachmentUploadGrid = Ext.extend(Tine.widgets.grid.FileUploadGrid, {
+    /**
+     * Store with all valid attachment types
+     */
+    attachmentTypeStore: null,
+
+    currentRecord: null,
+
+    initComponent: function () {
+        this.attachmentTypeStore = new Ext.data.JsonStore({
+            fields: ['id', 'name'],
+            data: this.getAttachmentMethods()
+        });
+
+        Tine.Felamimail.AttachmentUploadGrid.superclass.initComponent.call(this);
+
+        this.on('beforeedit', this.onBeforeEdit.createDelegate(this));
+    },
+
+    onBeforeEdit: function (e) {
+        var record = e.record;
+        this.currentRecord = record;
+    },
+
+    getAttachmentMethods: function () {
+        var methods = [{
+            id: 'attachment',
+            name: i18n._('Attachment')
+        }];
+
+        if (!Tine.Tinebase.appMgr.isEnabled('Filemanager')) {
+            return methods;
+        }
+
+        methods = methods.concat([{
+                id: 'download_public_fm',
+                name: i18n._('Filemanager (inline)')
+            }, {
+                id: 'download_protected_fm',
+                name: i18n._('Filemanager (inline, password)')
+            }, {
+                id: 'systemlink_fm',
+                name: i18n._('Filemanager (Systemlink)')
+            }]
+        );
+
+        return methods;
+    },
+
+    /**
+     * Override columns
+     */
+    getColumns: function () {
+        var me = this;
+
+        var combo = new Ext.form.ComboBox({
+            blurOnSelect: true,
+            expandOnFocus: true,
+            listWidth: 150,
+            mode: 'local',
+            value: 'attachment',
+            displayField: 'name',
+            valueField: 'id',
+            store: me.attachmentTypeStore,
+            disableKeyFilter: true,
+            queryMode: 'local'
+        });
+
+        combo.doQuery = function (q, forceAll, uploadGrid) {
+            this.store.clearFilter();
+
+            this.store.filterBy(function (record, id) {
+                var _ = window.lodash;
+
+                if (_.get(uploadGrid.currentRecord, 'data.type') === 'file' && !_.get(uploadGrid.currentRecord, 'data.account_grants.downloadGrant', true) && id === 'attachment') {
+                    return false;
+                }
+
+                // only fm files can be system links
+                if (_.get(uploadGrid.currentRecord, 'data.type') !== 'file' && id === 'systemlink_fm') {
+                    return false
+                }
+
+                // if no grants, then its not from fm
+                if (!_.get(uploadGrid.currentRecord, 'data.account_grants.publishGrant', true) && id.startsWith('download_')) {
+                    return false;
+                }
+
+                return true;
+            }.createDelegate(this, [uploadGrid.currentRecord], true));
+
+            this.onLoad();
+        }.createDelegate(combo, [this], true);
+
+        return [{
+            id: 'attachment_type',
+            dataIndex: 'attachment_type',
+            sortable: true,
+            width: 150,
+            header: i18n._('Attachment Type'),
+            tooltip: i18n._('Click icon to change'),
+            listeners: {},
+            value: 'attachment',
+            renderer: function (value) {
+                if (!value) {
+                    return null;
+                }
+
+                var record = me.attachmentTypeStore.getById(value);
+
+                if (!record) {
+                    return null;
+                }
+
+                return record.get('name');
+            },
+            editor: combo
+        }, {
+            resizable: true,
+            id: 'name',
+            dataIndex: 'name',
+            flex: 1,
+            header: i18n._('name'),
+            renderer: Ext.ux.PercentRendererWithName
+        }, {
+            resizable: true,
+            id: 'size',
+            dataIndex: 'size',
+            width: 70,
+            header: i18n._('size'),
+            renderer: Ext.util.Format.fileSize
+        }, {
+            resizable: true,
+            id: 'type',
+            dataIndex: 'type',
+            width: 70,
+            header: i18n._('type')
+        }]
+    },
+
+    onFilesSelect: function (fileSelector, e) {
+        var files = fileSelector.getFileList();
+        Ext.each(files, function (file) {
+
+            var upload = new Ext.ux.file.Upload({
+                file: file,
+                fileSelector: fileSelector
+            });
+
+            var uploadKey = Tine.Tinebase.uploadManager.queueUpload(upload);
+            var fileRecord = Tine.Tinebase.uploadManager.upload(uploadKey);
+
+            upload.on('uploadfailure', this.onUploadFail, this);
+            upload.on('uploadcomplete', this.onUploadComplete, fileRecord);
+            upload.on('uploadstart', Tine.Tinebase.uploadManager.onUploadStart, this);
+
+            if (fileRecord.get('status') !== 'failure') {
+                // overriden because of this
+                fileRecord.data.attachment_type = 'attachment';
+                this.store.add(fileRecord);
+            }
+
+
+        }, this);
+
+    },
+
+    onFileSelectFromFilemanager: function (nodes) {
+        var me = this;
+
+        Ext.each(nodes, function (node) {
+            var record = new Tine.Filemanager.Model.Node(node);
+
+            if (me.store.find('name', record.get('name')) === -1) {
+                // Overriden because of this
+                record.data.attachment_type = 'systemlink_fm';
+                me.store.add(record);
+            } else {
+                Ext.MessageBox.show({
+                    title: i18n._('Failure'),
+                    msg: i18n._('This file is already attached to this record.'),
+                    buttons: Ext.MessageBox.OK,
+                    icon: Ext.MessageBox.ERROR
+                });
+            }
+        });
+    },
+
+});
index 4040adc..ac36922 100644 (file)
@@ -143,6 +143,7 @@ Tine.Felamimail.MessageEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
         });
     },
 
         });
     },
 
+
     /**
      * init buttons
      */
     /**
      * init buttons
      */
@@ -993,7 +994,8 @@ Tine.Felamimail.MessageEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
         }
         
         this.attachmentGrid.store.each(function(attachment) {
         }
         
         this.attachmentGrid.store.each(function(attachment) {
-            this.record.data.attachments.push(Ext.ux.file.Upload.file.getFileData(attachment));
+            var fileData = Ext.copyTo({}, attachment.data, ['tempFile', 'name', 'path', 'size', 'type', 'id', 'attachment_type', 'password']);
+            this.record.data.attachments.push(fileData);
         }, this);
         
         var accountId = this.accountCombo.getValue(),
         }, this);
         
         var accountId = this.accountCombo.getValue(),
@@ -1015,7 +1017,7 @@ Tine.Felamimail.MessageEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
      */
     initAttachmentGrid: function() {
         if (! this.attachmentGrid) {
      */
     initAttachmentGrid: function() {
         if (! this.attachmentGrid) {
-            this.attachmentGrid = new Tine.widgets.grid.FileUploadGrid({
+            this.attachmentGrid = new Tine.Felamimail.AttachmentUploadGrid({
                 fieldLabel: this.app.i18n._('Attachments'),
                 hideLabel: true,
                 filesProperty: 'attachments',
                 fieldLabel: this.app.i18n._('Attachments'),
                 hideLabel: true,
                 filesProperty: 'attachments',
@@ -1053,7 +1055,7 @@ Tine.Felamimail.MessageEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
         
         var aliasAccount = null,
             aliases = null,
         
         var aliasAccount = null,
             aliases = null,
-            id = null
+            id = null;
             
         accountStore.each(function(account) {
             aliases = [ account.get('email') ];
             
         accountStore.each(function(account) {
             aliases = [ account.get('email') ];
@@ -1267,11 +1269,34 @@ Tine.Felamimail.MessageEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
      * 
      * TODO add note editing textfield here
      */
      * 
      * TODO add note editing textfield here
      */
-    onApplyChanges: function(closeWindow, emptySubject) {
+    onApplyChanges: function(closeWindow, emptySubject, passwordSet) {
+        var me = this;
+
         Tine.log.debug('Tine.Felamimail.MessageEditDialog::onApplyChanges()');
         
         this.loadMask.show();
 
         Tine.log.debug('Tine.Felamimail.MessageEditDialog::onApplyChanges()');
         
         this.loadMask.show();
 
+        // If filemanager attachments are possible check if passwords are required to enter
+        if (Tine.Tinebase.appMgr.isEnabled('Filemanager') && passwordSet !== true) {
+            var attachmentStore = this.attachmentGrid.getStore();
+
+            if (attachmentStore.find('attachment_type', 'download_protected_fm') !== -1) {
+                var dialog = new Tine.Tinebase.widgets.dialog.PasswordDialog();
+                dialog.openWindow();
+
+                dialog.on('passwordEntered', function (password) {
+                    attachmentStore.each(function (attachment) {
+                        if (attachment.get('attachment_type') === 'download_protected_fm') {
+                            attachment.data.password = password;
+                        }
+                    });
+
+                    me.onApplyChanges(closeWindow, emptySubject, true);
+                });
+                return;
+            }
+        }
+
         if (! emptySubject && this.getForm().findField('subject').getValue() == '') {
             Tine.log.debug('Tine.Felamimail.MessageEditDialog::onApplyChanges - empty subject');
             Ext.MessageBox.confirm(
         if (! emptySubject && this.getForm().findField('subject').getValue() == '') {
             Tine.log.debug('Tine.Felamimail.MessageEditDialog::onApplyChanges - empty subject');
             Ext.MessageBox.confirm(
@@ -1280,37 +1305,19 @@ Tine.Felamimail.MessageEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
                 function (button) {
                     Tine.log.debug('Tine.Felamimail.MessageEditDialog::doApplyChanges - button: ' + button);
                     if (button == 'yes') {
                 function (button) {
                     Tine.log.debug('Tine.Felamimail.MessageEditDialog::doApplyChanges - button: ' + button);
                     if (button == 'yes') {
-                        this.onApplyChanges(closeWindow, true);
+                        this.onApplyChanges(closeWindow, true, true);
                     } else {
                         this.loadMask.hide();
                     }
                 },
                 this
                     } else {
                         this.loadMask.hide();
                     }
                 },
                 this
-            )
+            );
+            
             return;
         }
 
         Tine.log.debug('Tine.Felamimail.MessageEditDialog::doApplyChanges - call parent');
         this.doApplyChanges(closeWindow);
             return;
         }
 
         Tine.log.debug('Tine.Felamimail.MessageEditDialog::doApplyChanges - call parent');
         this.doApplyChanges(closeWindow);
-
-        /*
-        if (this.record.data.note) {
-            // show message box with note editing textfield
-            //console.log(this.record.data.note);
-            Ext.Msg.prompt(
-                this.app.i18n._('Add Note'),
-                this.app.i18n._('Edit Email Note Text:'), 
-                function(btn, text) {
-                    if (btn == 'ok'){
-                        record.data.note = text;
-                    }
-                }, 
-                this,
-                100, // height of input area
-                this.record.data.body 
-            );
-        }
-        */
     },
     
     /**
     },
     
     /**
@@ -1318,7 +1325,7 @@ Tine.Felamimail.MessageEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
      * 
      * @return {Boolean}
      */
      * 
      * @return {Boolean}
      */
-        validateRecipients: function() {
+    validateRecipients: function() {
         var me = this;
         return new Promise(function (fulfill, reject) {
             var to = me.record.get('to'),
         var me = this;
         return new Promise(function (fulfill, reject) {
             var to = me.record.get('to'),
index edb3e7c..78039e1 100644 (file)
@@ -809,7 +809,7 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
     }
     
     /**
     }
     
     /**
-     * check file existance
+     * check file existence
      * 
      * @param Tinebase_Model_Tree_Node_Path $_path
      * @param Tinebase_Model_Tree_Node $_node
      * 
      * @param Tinebase_Model_Tree_Node_Path $_path
      * @param Tinebase_Model_Tree_Node $_node
@@ -1586,4 +1586,42 @@ class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
 
         return array('createdBy' => $createdBy, 'type' => $type);
     }
 
         return array('createdBy' => $createdBy, 'type' => $type);
     }
+
+    /**
+     * creates a node from a tempfile with download link in a defined folder
+     *
+     * - create folder path in Filemanager if it does not exist
+     * - create new file node from temp file
+     * - create download link for temp file
+     *
+     * @param Tinebase_Model_TempFile $tempFile
+     * @param string $_path
+     * @param string $_password
+     * @return Filemanager_Model_DownloadLink
+     */
+    public function createNodeWithDownloadLinkFromTempFile(Tinebase_Model_TempFile $_tempFile, $_path, $_password = '')
+    {
+        // check if path exists, if not: create
+        $folderPathRecord = Tinebase_Model_Tree_Node_Path::createFromPath($this->addBasePath($_path));
+        if (! $this->_backend->fileExists($folderPathRecord->statpath)) {
+            $this->_createNode($folderPathRecord, Tinebase_Model_Tree_FileObject::TYPE_FOLDER);
+        }
+
+        $filePathRecord = Tinebase_Model_Tree_Node_Path::createFromPath($this->addBasePath($_path . '/' . $_tempFile->name));
+        $filenode = $this->_createNode(
+            $filePathRecord,
+            Tinebase_Model_Tree_FileObject::TYPE_FILE,
+            $_tempFile->getId(),
+        // TODO always overwrite?
+            /* $_forceOverwrite */ true
+        );
+
+        $downloadLink = Filemanager_Controller_DownloadLink::getInstance()->create(new Filemanager_Model_DownloadLink(array(
+            'node_id'       => $filenode->getId(),
+            'expiry_date'   => Tinebase_DateTime::now()->addDay(30)->toString(),
+            'password'      => $_password
+        )));
+
+        return $downloadLink;
+    }
 }
 }
index 62ae9d8..443c0d1 100644 (file)
@@ -115,6 +115,7 @@ Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
                 layout: 'fit',
                 split: true,
                 frame: false,
                 layout: 'fit',
                 split: true,
                 frame: false,
+                border: false,
                 region: 'center',
                 width: 300,
                 items: [
                 region: 'center',
                 width: 300,
                 items: [
@@ -189,6 +190,8 @@ Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
             app: me.app,
             height: 200,
             width: 200,
             app: me.app,
             height: 200,
             width: 200,
+            border: false,
+            frame: false,
             readOnly: true,
             enableDD: false,
             enableDrag: false,
             readOnly: true,
             enableDD: false,
             enableDrag: false,
@@ -205,6 +208,9 @@ Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
             me.updateSelection(record);
         });
 
             me.updateSelection(record);
         });
 
+        // Hide filter toolbar
+        gridPanel.filterToolbar.hide();
+
         return gridPanel;
     },
 
         return gridPanel;
     },
 
index f78feba..2638d63 100644 (file)
@@ -811,6 +811,16 @@ class Tinebase_Frontend_Http extends Tinebase_Frontend_Http_Abstract
     public function downloadTempfile($tmpfileId)
     {
         $tmpFile = Tinebase_TempFile::getInstance()->getTempFile($tmpfileId);
     public function downloadTempfile($tmpfileId)
     {
         $tmpFile = Tinebase_TempFile::getInstance()->getTempFile($tmpfileId);
+
+        // some grids can house tempfiles and filemanager nodes, therefor first try tmpfile and if no tmpfile try filemanager
+        if (!$tmpFile && Tinebase_Application::getInstance()->isInstalled('Filemanager')) {
+            $filemanagerNodeController = Filemanager_Controller_Node::getInstance();
+            $file = $filemanagerNodeController->get($tmpfileId);
+
+            $filemanagerHttpFrontend = new Filemanager_Frontend_Http();
+            $filemanagerHttpFrontend->downloadFile($file->path, null);
+        }
+
         $this->_downloadFileNode($tmpFile, $tmpFile->path);
         exit;
     }
         $this->_downloadFileNode($tmpFile, $tmpFile->path);
         exit;
     }
index 673a940..35eb0c0 100644 (file)
  * @subpackage  Record
  * @property    string  name
  * @property    string  path
  * @subpackage  Record
  * @property    string  name
  * @property    string  path
+ * @property    string  id
+ * @property    string  session_id
+ * @property    int     size
+ * @property    string  type
+ * @property    string  time
  */
 class Tinebase_Model_TempFile extends Tinebase_Record_Abstract 
 {
  */
 class Tinebase_Model_TempFile extends Tinebase_Record_Abstract 
 {
index 6ac8ceb..cb5212f 100644 (file)
@@ -28,7 +28,7 @@ Ext.ns('Tine.widgets.grid');
  * 
  * @constructor Create a new  Tine.widgets.grid.FileUploadGrid
  */
  * 
  * @constructor Create a new  Tine.widgets.grid.FileUploadGrid
  */
-Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
+Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.EditorGridPanel, {
 
     /**
      * @cfg filesProperty
 
     /**
      * @cfg filesProperty
@@ -79,8 +79,14 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
         this.initStore();
         this.initColumnModel();
         this.initSelectionModel();
         this.initStore();
         this.initColumnModel();
         this.initSelectionModel();
-        
-        this.plugins = [ new Ext.ux.grid.GridViewMenuPlugin({}) ];
+
+
+        if (!this.plugins) {
+            this.plugins = [];
+        }
+
+        this.plugins.push(new Ext.ux.grid.GridViewMenuPlugin({}));
+
         this.enableHdMenu = false;
       
         Tine.widgets.grid.FileUploadGrid.superclass.initComponent.call(this);
         this.enableHdMenu = false;
       
         Tine.widgets.grid.FileUploadGrid.superclass.initComponent.call(this);
@@ -94,8 +100,14 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
             this.contextMenu.showAt(e.getXY());
         }, this);
 
             this.contextMenu.showAt(e.getXY());
         }, this);
 
-        if (! this.record || this.record.id == 0) {
-            this.on('rowdblclick', function (grid, row, e) {
+        if (! this.record || this.record.id === 0) {
+            this.on('celldblclick', function (grid, rowIndex, columnIndex, e) {
+                // Don't download if the cell has an editor, just go on with the event
+                if (grid.getColumns()[columnIndex].hasOwnProperty('editor')) {
+                    return true;
+                }
+
+                // In case cell has no editor, just assume a download is intended
                 e.stopEvent();
                 this.onDownload()
             }, this);
                 e.stopEvent();
                 this.onDownload()
             }, this);
@@ -301,7 +313,7 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
                 xhr.onprogress = function (e) {
                     var progress = Math.floor(100 * e.loaded / e.total) + '% loaded';
                     console.log(e);
                 xhr.onprogress = function (e) {
                     var progress = Math.floor(100 * e.loaded / e.total) + '% loaded';
                     console.log(e);
-                }
+                };
 
 
                 xhr.onload = function (e) {
 
 
                 xhr.onload = function (e) {
@@ -388,7 +400,6 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
     
     /**
      * init cm
     
     /**
      * init cm
-     * @private
      */
     initColumnModel: function () {
         this.cm = new Ext.grid.ColumnModel(this.getColumns());
      */
     initColumnModel: function () {
         this.cm = new Ext.grid.ColumnModel(this.getColumns());