Merge pull request #44 from matthiasblaesing/improve-pp3

Improve PP3
diff --git a/pp3/config/pp3.sql b/pp3/config/pp3.sql
index 9374896..6cf372e 100644
--- a/pp3/config/pp3.sql
+++ b/pp3/config/pp3.sql
@@ -144,6 +144,16 @@
 ALTER TABLE nb_version ADD COLUMN catalog_rebuild datetime DEFAULT NULL;
 ALTER TABLE plugin_version ADD COLUMN error_message text COLLATE utf8_czech_ci DEFAULT NULL;
 
+CREATE TABLE `plugin_user` (
+    plugin_id int(11) NOT NULL REFERENCES plugin(id),
+    user_id int(11) NOT NULL REFERENCES user(id),
+    PRIMARY KEY (`plugin_id`,`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;
+
+insert into plugin_user SELECT id, author_id FROM plugin;
+
+ALTER TABLE plugin DROP COLUMN author_id;
+
 /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
 /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
 /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/pp3/module/Application/src/Application/Controller/AdminController.php b/pp3/module/Application/src/Application/Controller/AdminController.php
index 739668e..f9aac9a 100644
--- a/pp3/module/Application/src/Application/Controller/AdminController.php
+++ b/pp3/module/Application/src/Application/Controller/AdminController.php
@@ -323,7 +323,7 @@
         }
         return new ViewModel([
             'nbVersions' => $this->_nbVersionRepository->findAll()
-        ]);;
+        ]);
     }
 
     private function _createEmptyCatalogForNbVersion($nbVersion) {
@@ -344,8 +344,9 @@
                 $this->getDownloadBaseUrl(),
                 $this->_config['pp3']['catalogSavepath']);
         try {
-            $catalog->storeXml(true);
-            $catalogExp->storeXml(true);
+            $errors = [];
+            $catalog->storeXml(true, $errors);
+            $catalogExp->storeXml(true, $errors);
         } catch (\Exception $e){
             $this->flashMessenger()->setNamespace('error')->addMessage($e->getMessage());                        
         }         
@@ -375,9 +376,10 @@
                     $this->getDownloadBaseUrl(),
                     $this->_config['pp3']['catalogSavepath']);
             try {
-                $catalog->storeXml(true);
-            } catch (\Exception $e) { }                 
-            
+                $errors = [];
+                $catalog->storeXml(true, $errors);
+            } catch (\Exception $e) { }
+
             $catalog = new Catalog(
                     $this->_pluginVersionRepository,
                     $version,
@@ -387,9 +389,10 @@
                     $this->getDownloadBaseUrl(),
                     $this->_config['pp3']['catalogSavepath']);
             try {
-                $catalog->storeXml(true);
+                $errors = [];
+                $catalog->storeXml(true, $errors);
             } catch (\Exception $e) { }                 
-            
+
         }
     }
 
@@ -404,60 +407,102 @@
     public function editAction() {
         $this->_checkAdminUser();
         $pId = $this->params()->fromQuery('id');
-        $plugin = $this->_pluginRepository->find($pId);        
+        $plugin = $this->_pluginRepository->find($pId);
         if (!$plugin || empty($pId)) {
             return $this->redirect()->toRoute('admin');
         }
         $req = $this->request;
         if ($req->isPost()) {
-            $validatedData = $this->_validateAndCleanPluginData(
-                $this->params()->fromPost('name'),
-                $this->params()->fromPost('license'),
-                $this->params()->fromPost('description'),
-                $this->params()->fromPost('short_description'),
-                $this->params()->fromPost('category')
-            );
-            if ($validatedData) {
-                $plugin->setName($validatedData['name']);
-                $plugin->setLicense($validatedData['license']);
-                $plugin->setDescription($validatedData['description']);
-                $plugin->setShortDescription($validatedData['short_description']);
-                $plugin->setLastUpdatedAt(new \DateTime('now'));                
-
-                // save image
-                $im = $this->handleImgUpload($this->_config['pp3']['catalogSavepath'].'/plugins/'.$plugin->getId());
-                if ($im) {                    
-                    $plugin->setImage($im);
+            $queryString = "";
+            if ($this->params()->fromPost('addUserByEMail')) {
+                $queryString = '&activeTab=authors';
+                $users = $this->_userRepository->findByEmail($this->params()->fromPost('addUserByEMail'));
+                if (count($users) > 0) {
+                    $added = 0;
+                    foreach ($users as $user) {
+                        if($plugin->addAuthor($user)) {
+                            $added++;
+                        }
+                    }
+                    if ($added > 0) {
+                        $this->flashMessenger()->setNamespace('success')->addMessage('Authors updated.');
+                        $this->_pluginRepository->persist($plugin);
+                    } else {
+                        $this->flashMessenger()->setNamespace('warning')->addMessage('Cannot add the same user twice.');
+                    }
+                } else {
+                    $this->flashMessenger()->setNamespace('error')->addMessage('No user found with specified address.');
                 }
-
-
-                // categ
-                $plugin->removeCategories();
-                $this->_pluginRepository->persist($plugin);
-                $cat = $this->_categoryRepository->find($validatedData['category']);
-                if ($cat) {
-                    $plugin->addCategory($cat);
-                }
-                $cat2 = $this->params()->fromPost('category2');
-                if ($cat2 && (!$cat || ($cat2 != $cat->getId()))) {
-                    $cat2 = $this->_categoryRepository->find($this->params()->fromPost('category2'));
-                    if ($cat2) {
-                        //die(var_dump($cat2, $plugin->getCategories()[1], $validatedData['category']));
-                        $plugin->addCategory($cat2);
+            } else if ($this->params()->fromPost('removeAuthor')) {
+                $queryString = '&activeTab=authors';
+                $user = $this->_userRepository->find($this->params()->fromPost('removeAuthor'));
+                if (!$user) {
+                    $this->flashMessenger()->setNamespace('error')->addMessage('No user found with specified id.');
+                } else {
+                    $plugin->removeAuthor($user);
+                    if (count($plugin->getAuthors()) > 0) {
+                        $this->_pluginRepository->persist($plugin);
+                        $this->flashMessenger()->setNamespace('success')->addMessage('Author was removed.');
+                    } else {
+                        $this->flashMessenger()->setNamespace('error')->addMessage('Last author can not be removed.');
                     }
                 }
-
-                $this->_pluginRepository->persist($plugin);
-                $this->flashMessenger()->setNamespace('success')->addMessage('Plugin updated.');               
             } else {
-                $this->flashMessenger()->setNamespace('error')->addMessage('Missing required data.');
+                $validatedData = $this->_validateAndCleanPluginData(
+                        $this->params()->fromPost('name'),
+                        $this->params()->fromPost('license'),
+                        $this->params()->fromPost('description'),
+                        $this->params()->fromPost('short_description'),
+                        $this->params()->fromPost('category')
+                );
+                if ($validatedData) {
+                    $plugin->setName($validatedData['name']);
+                    $plugin->setLicense($validatedData['license']);
+                    $plugin->setDescription($validatedData['description']);
+                    $plugin->setShortDescription($validatedData['short_description']);
+                    $plugin->setLastUpdatedAt(new \DateTime('now'));
+
+                    // save image
+                    $im = $this->handleImgUpload($this->_config['pp3']['catalogSavepath'] . '/plugins/' . $plugin->getId());
+                    if ($im) {
+                        $plugin->setImage($im);
+                    }
+
+
+                    // categ
+                    $plugin->removeCategories();
+                    $this->_pluginRepository->persist($plugin);
+                    $cat = $this->_categoryRepository->find($validatedData['category']);
+                    if ($cat) {
+                        $plugin->addCategory($cat);
+                    }
+                    $cat2 = $this->params()->fromPost('category2');
+                    if ($cat2 && (!$cat || ($cat2 != $cat->getId()))) {
+                        $cat2 = $this->_categoryRepository->find($this->params()->fromPost('category2'));
+                        if ($cat2) {
+                            $plugin->addCategory($cat2);
+                        }
+                    }
+
+                    $this->_pluginRepository->persist($plugin);
+                    $this->flashMessenger()->setNamespace('success')->addMessage('Plugin updated.');
+                } else {
+                    $this->flashMessenger()->setNamespace('error')->addMessage('Missing required data.');
+                }
             }
-            return $this->redirect()->toUrl('./edit?id='.$plugin->getId());      
+            return $this->redirect()->toUrl('./edit?id=' . $plugin->getId() . $queryString);
+        }
+
+        $activeTab = $this->params()->fromQuery("activeTab");
+
+        if (!($activeTab == 'settings' || $activeTab == 'authors')) {
+            $activeTab = 'settings';
         }
 
         return new ViewModel(array(
             'plugin' => $plugin,
             'categories' => $this->_categoryRepository->getAllCategoriesSortByName(),
+            'activeTab' => $activeTab
         ));
     }
 
diff --git a/pp3/module/Application/src/Application/Controller/CliController.php b/pp3/module/Application/src/Application/Controller/CliController.php
index 6f9f3df..85c773a 100644
--- a/pp3/module/Application/src/Application/Controller/CliController.php
+++ b/pp3/module/Application/src/Application/Controller/CliController.php
@@ -125,7 +125,9 @@
         }
         $plugin = $pluginVersion->getPlugin();
         $mail = new Mail\Message();
-        $mail->addTo($plugin->getAuthor()->getEmail());
+        foreach($plugin->getAuthors() as $user) {
+            $mail->addTo($user->getEmail());
+        }
         $mail->setFrom('webmaster@netbeans.apache.org', 'NetBeans webmaster');
         $mail->setSubject('Publishing of your plugin '.$plugin->getName().' failed');
         $mail->setBody('Hello plugin owner,
diff --git a/pp3/module/Application/src/Application/Controller/PluginController.php b/pp3/module/Application/src/Application/Controller/PluginController.php
index 5176b13..bdb6cef 100644
--- a/pp3/module/Application/src/Application/Controller/PluginController.php
+++ b/pp3/module/Application/src/Application/Controller/PluginController.php
@@ -32,9 +32,21 @@
 
 class PluginController extends AuthenticatedController {
 
+    /**
+     * @var \Application\Repository\PluginRepository
+     */
     private $_pluginRepository;
+    /**
+     * @var \Application\Repository\PluginVersionRepository
+     */
     private $_pluginVersionRepository;
+    /**
+     * @var \Application\Repository\CategoryRepository
+     */
     private $_categoryRepository;
+    /**
+     * @var \Application\Repository\NbVersionRepository
+     */
     private $_nbVersionRepository;
     /**
      * @var \Application\Repository\UserRepository
@@ -96,12 +108,10 @@
             
             if (!empty($groupId) && !empty($artifactId)) {
                 $url = $this->_config['pp3']['mavenRepoUrl'].str_replace('.','/', $groupId).'/'.$artifactId.'/maven-metadata.xml';
-                $user = $this->_userRepository->find($this->getAuthenticatedUserId());
                 $plugin->setUrl($url);
                 $plugin->setArtifactId($artifactId);
                 $plugin->setGroupId($groupId);
                 $plugin->setStatus(Plugin::STATUS_PRIVATE);
-                $plugin->setAuthor($user);
                 $plugin->setDataLoader(new MavenDataLoader());
                 try {
                     if ($plugin->loadData()) {
@@ -153,7 +163,7 @@
             );
             if ($validatedData) {
                 $user = $this->_userRepository->find($this->getAuthenticatedUserId());
-                $plugin->setAuthor($user);
+                $plugin->addAuthor($user);
                 $plugin->setName($validatedData['name']);
                 $plugin->setLicense($validatedData['license']);
                 $plugin->setDescription($validatedData['description']);
@@ -219,56 +229,98 @@
         }
         $req = $this->request;
         if ($req->isPost()) {
-            $validatedData = $this->_validateAndCleanPluginData(
-                $this->params()->fromPost('name'),
-                $this->params()->fromPost('license'),
-                $this->params()->fromPost('description'),
-                $this->params()->fromPost('short_description'),
-                $this->params()->fromPost('category'),
-                $this->params()->fromPost('homepage')
-            );
-            if ($validatedData) {
-                $plugin->setName($validatedData['name']);
-                $plugin->setLicense($validatedData['license']);
-                $plugin->setDescription($validatedData['description']);
-                $plugin->setShortDescription($validatedData['short_description']);
-                $plugin->setHomepage($validatedData['homepage']);
-                $plugin->setLastUpdatedAt(new \DateTime('now'));                
-                
-                // save image
-                $im = $this->handleImgUpload($this->_config['pp3']['catalogSavepath'].'/plugins/'.$plugin->getId());
-                if ($im) {                    
-                    $plugin->setImage($im);
+            $queryString = "";
+            if ($this->params()->fromPost('addUserByEMail')) {
+                $queryString='&activeTab=authors';
+                $users = $this->_userRepository->findByEmail($this->params()->fromPost('addUserByEMail'));
+                if (count($users) > 0) {
+                    $added = 0;
+                    foreach ($users as $user) {
+                        if($plugin->addAuthor($user)) {
+                            $added++;
+                        }
+                    }
+                    if ($added > 0) {
+                        $this->flashMessenger()->setNamespace('success')->addMessage('Authors updated.');
+                        $this->_pluginRepository->persist($plugin);
+                    } else {
+                        $this->flashMessenger()->setNamespace('warning')->addMessage('Cannot add the same user twice.');
+                    }
+                } else {
+                    $this->flashMessenger()->setNamespace('error')->addMessage('No user found with specified address.');
                 }
-
-                // categ
-                $plugin->removeCategories();
-                $this->_pluginRepository->persist($plugin);
-                $cat = $this->_categoryRepository->find($validatedData['category']);
-                if ($cat) {
-                    $plugin->addCategory($cat);
-                }
-                $cat2 = $this->params()->fromPost('category2');
-                if ($cat2 && (!$cat || ($cat2 != $cat->getId()))) {
-                    $cat2 = $this->_categoryRepository->find($this->params()->fromPost('category2'));
-                    if ($cat2) {
-                        //die(var_dump($cat2, $plugin->getCategories()[1], $validatedData['category']));
-                        $plugin->addCategory($cat2);
+            } else if ($this->params()->fromPost('removeAuthor')) {
+                $queryString='&activeTab=authors';
+                $user = $this->_userRepository->find($this->params()->fromPost('removeAuthor'));
+                if(! $user) {
+                    $this->flashMessenger()->setNamespace('error')->addMessage('No user found with specified id.');
+                } else {
+                    $plugin->removeAuthor($user);
+                    if (count($plugin->getAuthors()) > 0) {
+                        $this->_pluginRepository->persist($plugin);
+                        $this->flashMessenger()->setNamespace('success')->addMessage('Author was removed.');
+                    } else {
+                        $this->flashMessenger()->setNamespace('error')->addMessage('Last author can not be removed.');
                     }
                 }
-
-                $this->_pluginRepository->persist($plugin);
-
-                $this->flashMessenger()->setNamespace('success')->addMessage('Plugin updated.');               
             } else {
-                $this->flashMessenger()->setNamespace('error')->addMessage('Missing required data.');
+                $validatedData = $this->_validateAndCleanPluginData(
+                        $this->params()->fromPost('name'),
+                        $this->params()->fromPost('license'),
+                        $this->params()->fromPost('description'),
+                        $this->params()->fromPost('short_description'),
+                        $this->params()->fromPost('category'),
+                        $this->params()->fromPost('homepage')
+                );
+                if ($validatedData) {
+                    $plugin->setName($validatedData['name']);
+                    $plugin->setLicense($validatedData['license']);
+                    $plugin->setDescription($validatedData['description']);
+                    $plugin->setShortDescription($validatedData['short_description']);
+                    $plugin->setHomepage($validatedData['homepage']);
+                    $plugin->setLastUpdatedAt(new \DateTime('now'));
+
+                    // save image
+                    $im = $this->handleImgUpload($this->_config['pp3']['catalogSavepath'] . '/plugins/' . $plugin->getId());
+                    if ($im) {
+                        $plugin->setImage($im);
+                    }
+
+                    // categ
+                    $plugin->removeCategories();
+                    $this->_pluginRepository->persist($plugin);
+                    $cat = $this->_categoryRepository->find($validatedData['category']);
+                    if ($cat) {
+                        $plugin->addCategory($cat);
+                    }
+                    $cat2 = $this->params()->fromPost('category2');
+                    if ($cat2 && (!$cat || ($cat2 != $cat->getId()))) {
+                        $cat2 = $this->_categoryRepository->find($this->params()->fromPost('category2'));
+                        if ($cat2) {
+                            $plugin->addCategory($cat2);
+                        }
+                    }
+
+                    $this->_pluginRepository->persist($plugin);
+
+                    $this->flashMessenger()->setNamespace('success')->addMessage('Plugin updated.');
+                } else {
+                    $this->flashMessenger()->setNamespace('error')->addMessage('Missing required data.');
+                }
             }
-            return $this->redirect()->toUrl('./edit?id='.$plugin->getId());      
+            return $this->redirect()->toUrl('./edit?id='.$plugin->getId() . $queryString);
+        }
+
+        $activeTab = $this->params()->fromQuery("activeTab");
+
+        if(!($activeTab == 'settings' || $activeTab == 'authors')) {
+            $activeTab = 'settings';
         }
 
         return new ViewModel(array(
             'plugin' => $plugin,
             'categories' => $this->_categoryRepository->getAllCategoriesSortByName(),
+            'activeTab' => $activeTab
         ));
     }
 
diff --git a/pp3/module/Application/src/Application/Controller/VerificationController.php b/pp3/module/Application/src/Application/Controller/VerificationController.php
index 1a68c51..294f118 100644
--- a/pp3/module/Application/src/Application/Controller/VerificationController.php
+++ b/pp3/module/Application/src/Application/Controller/VerificationController.php
@@ -200,7 +200,9 @@
         $nbVersion = $verification->getNbVersionPluginVersion()->getNbVersion()->getVersion();
         $pluginVersion = $verification->getNbVersionPluginVersion()->getPluginVersion()->getVersion();
         $mail = new Mail\Message();
-        $mail->addTo($plugin->getAuthor()->getEmail());
+        foreach($plugin->getAuthors() as $user) {
+            $mail->addTo($user->getEmail());
+        }
         $mail->setFrom('webmaster@netbeans.apache.org', 'NetBeans webmaster');
         $mail->setSubject('Verification of your '.$plugin->getName().' is complete');
         $mail->setBody('Hello plugin owner,
@@ -224,11 +226,14 @@
     }
 
     private function _sendGoNotification($verification, $comment) {
+        /* @var $plugin Application\Entity\Plugin */
         $plugin = $verification->getNbVersionPluginVersion()->getPluginVersion()->getPlugin();
         $nbVersion = $verification->getNbVersionPluginVersion()->getNbVersion()->getVersion();
         $pluginVersion = $verification->getNbVersionPluginVersion()->getPluginVersion()->getVersion();
         $mail = new Mail\Message();
-        $mail->addTo($plugin->getAuthor()->getEmail());
+        foreach($plugin->getAuthors() as $user) {
+            $mail->addTo($user->getEmail());
+        }
         $mail->setFrom('webmaster@netbeans.apache.org', 'NetBeans webmaster');
         $mail->setSubject('Verification of your '.$plugin->getName().' is complete');
         $mail->setBody('Hello plugin owner,
diff --git a/pp3/module/Application/src/Application/Entity/Base/Plugin.php b/pp3/module/Application/src/Application/Entity/Base/Plugin.php
index 025e0e4..65fcd92 100644
--- a/pp3/module/Application/src/Application/Entity/Base/Plugin.php
+++ b/pp3/module/Application/src/Application/Entity/Base/Plugin.php
@@ -50,10 +50,11 @@
     /** @ORM\Column(type="string", length=255) */
     protected $license;
 
-    /** 
-     * @ORM\ManyToOne(targetEntity="User") 
+    /**
+     * @ORM\ManyToMany(targetEntity="User")
+     * @ORM\JoinTable(name="plugin_user")
      */
-    protected $author;
+    protected $authors;
 
     /** @ORM\Column(type="datetime") */
     protected $added_at;
@@ -99,6 +100,7 @@
     public function __construct() {
         $this->versions = new ArrayCollection();
         $this->categories = new ArrayCollection();
+        $this->authors = new ArrayCollection();
         return $this;
     }
 
@@ -159,17 +161,10 @@
     }
 
     /**
-     * @return User
+     * @return User[]
      */
-    public function getAuthor() {
-        return $this->author;
-    }
-
-    /**
-     * @param User $author
-     */
-    public function setAuthor($author) {
-        $this->author = $author;
+    public function getAuthors() {
+        return $this->authors;
     }
 
     public function getAddedAt() {
diff --git a/pp3/module/Application/src/Application/Entity/Plugin.php b/pp3/module/Application/src/Application/Entity/Plugin.php
index ae4c033..eee48e1 100644
--- a/pp3/module/Application/src/Application/Entity/Plugin.php
+++ b/pp3/module/Application/src/Application/Entity/Plugin.php
@@ -116,6 +116,24 @@
         }
     }
 
+    public function removeAuthor($user) {
+        $this->authors->removeElement($user);
+    }
+
+    /**
+     * @param User $user
+     * @return boolean true if author was added, false if it already existed
+     */
+    public function addAuthor($user) {
+        foreach($this->authors as $existingUser) {
+            if($user->getId() == $existingUser->getId()) {
+                return false;
+            }
+        }
+        $this->authors[] = $user;
+        return true;
+    }
+
     public function addVersion($version) {
         $this->versions[] = $version;
     }
@@ -162,7 +180,12 @@
     }
 
     public function isOwnedBy($userId) {
-        return $this->getAuthor()->getId() == $userId;
+        foreach($this->getAuthors() as $author) {
+            if($author->getId() == $userId) {
+                return true;
+            }
+        }
+        return false;
     }
 
     public function setUrl($url) {
diff --git a/pp3/module/Application/src/Application/Repository/PluginRepository.php b/pp3/module/Application/src/Application/Repository/PluginRepository.php
index 5f213fd..2e6b4ae 100644
--- a/pp3/module/Application/src/Application/Repository/PluginRepository.php
+++ b/pp3/module/Application/src/Application/Repository/PluginRepository.php
@@ -74,7 +74,7 @@
                 ->leftJoin('v.nbVersionsPluginVersions', 'nbvPv')
                 ->leftJoin('nbvPv.nbVersion', 'nbv')
                 ->leftJoin('nbvPv.verification', 'verif')
-                ->leftJoin('p.author', 'a')
+                ->leftJoin('p.authors', 'a')
                 ->where('a.id = :author')
                 ->orderBy('p.id', 'DESC')
                 ->setParameter('author', $author);
@@ -100,7 +100,7 @@
         ->leftJoin('p.versions', 'v')
         ->leftJoin('v.nbVersionsPluginVersions', 'nbvPv')
         ->leftJoin('nbvPv.nbVersion', 'nbv')
-        ->leftJoin('p.author', 'a')
+        ->leftJoin('p.authors', 'a')
         ->where('(p.name LIKE :name) OR (a.email LIKE :name)')->orderBy('p.id', 'ASC')
         ->setParameter('name', '%'.$name.'%');
         return $queryBuilder->getQuery()->getResult();
@@ -115,7 +115,7 @@
         ->leftJoin('nbvPv.verification', 'verif')
         ->leftJoin('nbvPv.nbVersion', 'nbv')
         ->leftJoin('p.categories', 'cat')
-        ->leftJoin('p.author', 'a')
+        ->leftJoin('p.authors', 'a')
         ->where('p.status = :status')
         ->setParameter('status', \Application\Entity\Plugin::STATUS_PUBLIC);
         if ($name) {
diff --git a/pp3/module/Application/view/application/admin/_plugin-listrow.phtml b/pp3/module/Application/view/application/admin/_plugin-listrow.phtml
index adc8ca8..cd8c75d 100644
--- a/pp3/module/Application/view/application/admin/_plugin-listrow.phtml
+++ b/pp3/module/Application/view/application/admin/_plugin-listrow.phtml
@@ -19,7 +19,12 @@
  */
 ?>
 <?php
-$pluugin = $this->plugin;
+/* @var $plugin Application\Entity\Plugin */
+$authors = [];
+foreach($plugin->getAuthors() as $author) {
+    $authors[] = htmlspecialchars($author->getName(), ENT_COMPAT, "UTF-8");
+}
+
 echo '<tr>
 <td style="text-align: center;"><h4><i class="fas '.$plugin->getStatusIconClass().'" title="'.$plugin->getStatusTitle().'"></i></h4></td>
 <td>
@@ -41,16 +46,17 @@
         <i class="fas fa-eye-slash color-red"></i>&nbsp; Hide
         </a>
     </p>
-    <p>Status: <b>'.$plugin->getStatusTitle().'</b></p>
-    <div>
-        <p>ArtifactId: <b>'.$plugin->getArtifactId().'</b><p>
-        <p>Author: <b>'.$plugin->getAuthor()->getName().'</b><p>
-        <p>
-        <i class="fas fa-asterisk"></i> '.$plugin->getAddedAt()->format('Y-m-d').' &nbsp; &nbsp;
-        <i class="fas fa-edit"></i> '.$plugin->getLastUpdatedAt()->format('Y-m-d').' &nbsp; &nbsp; 
-        <i class="fas fa-download"></i> '.number_format($plugin->getDownloads()).' 
-        </p>                
-    </div>
+    <table role="presentation">
+        <tr><td style="padding-right: 1ex">Status:</td><td><b>'.$plugin->getStatusTitle().'</b></td></tr>
+        <tr><td style="padding-right: 1ex">GroupId: </td><td><b>'.$plugin->getGroupId().'</b></td></tr>
+        <tr><td style="padding-right: 1ex">ArtifactId:</td><td><b>'.$plugin->getArtifactId().'</b></td></tr>
+        <tr><td style="padding-right: 1ex">Author:</td><td><b>'. implode("<br />", $authors) .'</b></td></tr>
+    </table>
+    <p>
+    <i class="fas fa-asterisk"></i> '.$plugin->getAddedAt()->format('Y-m-d').' &nbsp; &nbsp;
+    <i class="fas fa-edit"></i> '.$plugin->getLastUpdatedAt()->format('Y-m-d').' &nbsp; &nbsp; 
+    <i class="fas fa-download"></i> '.number_format($plugin->getDownloads()).' 
+    </p>
     <p>';
 
 foreach ($plugin->getCategories() as $cat) {
diff --git a/pp3/module/Application/view/application/admin/_pluginRowItem.phtml b/pp3/module/Application/view/application/admin/_pluginRowItem.phtml
index 6791d13..c72c530 100644
--- a/pp3/module/Application/view/application/admin/_pluginRowItem.phtml
+++ b/pp3/module/Application/view/application/admin/_pluginRowItem.phtml
@@ -24,15 +24,24 @@
     $versionBadges[]='<span class="badge">'.$version->getVersion().'</span>';
 }
 
+/* @var $plugin Application\Entity\Plugin */
+$authors = [];
+foreach($plugin->getAuthors() as $author) {
+    $authors[] = htmlspecialchars($author->getName(), ENT_COMPAT, "UTF-8");
+}
+
 echo '
 <h4 class="text-primary"><a href="'.$this->url('admin', array('action' => 'edit'),array('query' => array('id'=>$plugin->getId()))).'">'.$plugin->getName().'</a></h4>
 <p>
     '.implode('&nbsp;', $versionBadges).'
 </p>
-<p>ArtifactId: <b>'.$plugin->getArtifactId().'</b><p>
+<table role="presentation">
+    <tr><td style="padding-right: 1ex">Status:</td><td><b>'.$plugin->getStatusTitle().'</b></td></tr>
+    <tr><td style="padding-right: 1ex">GroupId: </td><td><b>'.$plugin->getGroupId().'</b></td></tr>
+    <tr><td style="padding-right: 1ex">ArtifactId:</td><td><b>'.$plugin->getArtifactId().'</b></td></tr>
+    <tr><td style="padding-right: 1ex">Author:</td><td><b>'. implode("<br />", $authors) .'</b></td></tr>
+</table>
 <p>
-<p>Status: <b>'.$plugin->getStatusTitle().'</b></p>
-<i class="fas fa-user"></i> '.$plugin->getAuthor()->getName().' &nbsp; &nbsp;&nbsp;
 <i class="fas fa-asterisk"></i> '.$plugin->getAddedAt()->format('Y-m-d').' &nbsp; &nbsp;&nbsp; 
 <i class="fas fa-edit"></i> '.$plugin->getLastUpdatedAt()->format('Y-m-d').'   &nbsp; &nbsp;  &nbsp;            
 <i class="fas fa-file-contract"></i> '.$plugin->getLicense().'
diff --git a/pp3/module/Application/view/application/admin/edit.phtml b/pp3/module/Application/view/application/admin/edit.phtml
index 431bead..2a51f1a 100644
--- a/pp3/module/Application/view/application/admin/edit.phtml
+++ b/pp3/module/Application/view/application/admin/edit.phtml
@@ -19,14 +19,52 @@
  */
 ?>
 <h3>Edit Plugin</h3>
-    <div class="row">
-        <div class="col col-sm-6">
-            <?= $this->partial('layout/flash.phtml'); ?>
-            <form method="post" action="" class="needs-validation" enctype="multipart/form-data">
-            <?= $this->partial('application/plugin/_plugin-form.phtml', array('plugin' => $this->plugin, 'categories' => $this->categories)); ?>
-            <a class="btn btn-secondary" href="<?= $this->url('admin')?>" role="button">Return</a>
-            <button type="submit" class="btn btn-primary">Save Plugin</button>
-            </form>
-            
+<?= $this->partial('layout/flash.phtml'); ?>
+<div class="">
+    <ul class="nav nav-tabs" role="tablist">
+        <li role="presentation" class="<?= $this->activeTab == 'settings' ? 'active' : '' ?>"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Plugin Settings</a></li>
+        <li role="presentation" class="<?= $this->activeTab == 'authors' ? 'active' : '' ?>"><a href="#authors" aria-controls="authors" role="tab" data-toggle="tab">Authors</a></li>
+    </ul>
+    <div class="tab-content">
+        <div class="tab-pane panel panel-default <?= $this->activeTab == 'settings' ? 'active' : '' ?>" style="padding: 2ex 1ex" role="tabpanel" id="settings">
+            <div class="row">
+                <div class="col col-sm-6">
+                    <form method="post" action="" class="needs-validation" enctype="multipart/form-data">
+                        <?= $this->partial('application/plugin/_plugin-form.phtml', array('plugin' => $this->plugin, 'categories' => $this->categories)); ?>
+                        <a class="btn btn-secondary" href="<?= $this->url('admin') ?>" role="button">Return</a>
+                        <button type="submit" class="btn btn-primary">Save Plugin</button>
+                    </form>
+
+                </div>
+            </div>
         </div>
-    </div>
\ No newline at end of file
+        <div class="tab-pane panel panel-default <?= $this->activeTab == 'authors' ? 'active' : '' ?>" style="padding: 2ex 1ex; position: relative" role="tabpanel" id="authors">
+            <div class="row">
+                <div class="col col-sm-6">
+                    <h4>List of authors</h4>
+                    <table role="presentation">
+                        <?php
+                        foreach ($this->plugin->getAuthors() as $author) {
+                            printf('<tr><td>%s (%s, %d)</td><td><form method="post" action="" enctype="multipart/form-data" style="display: inline-block; margin-left: 1ex"><button class="btn btn-default" role="button" name="removeAuthor" value="%d"><i class="fas fa-trash text-danger"></i></button></a></form></td></tr>',
+                                    htmlspecialchars($author->getName(), ENT_COMPAT, 'UTF-8'),
+                                    htmlspecialchars($author->getEmail(), ENT_COMPAT, 'UTF-8'),
+                                    $author->getId(),
+                                    $author->getId()
+                            );
+                        }
+                        ?>
+                    </table>
+                    <h4>Add author</h4>
+                    <form method="post" action="" enctype="multipart/form-data" style="display: inline">
+                        <div class="form-group">
+                            <label for="urgroupIdl">E-mail</label>
+                            <input type="text" class="form-control" id="addUserByEMail" name="addUserByEMail" placeholder="Please enter e-mail" value="" >
+                        </div>
+                        <a class="btn btn-secondary" href="<?= $this->url('admin') ?>" role="button">Return</a>
+                        <button type="submit" class="btn btn-primary">Add author</button>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/pp3/module/Application/view/application/index/catalogue.phtml b/pp3/module/Application/view/application/index/catalogue.phtml
index 42d2179..0c7f7c2 100644
--- a/pp3/module/Application/view/application/index/catalogue.phtml
+++ b/pp3/module/Application/view/application/index/catalogue.phtml
@@ -26,21 +26,22 @@
 echo '<h3 class="text-primary">'.$h.'</h3>';
 
 if ($plugin) {
+    /* @var $plugin Application\Entity\Plugin */
+    $authors = [];
+    foreach($plugin->getAuthors() as $author) {
+        $authors[] = htmlspecialchars($author->getName(), ENT_COMPAT, "UTF-8");
+    }
+
     echo '
     <div class="row">
     <div class="col-md-6">
-    <p>
-    <p>ArtifactId: <b>'.$plugin->getArtifactId().'</b> &nbsp;&nbsp;
-    ';
-
-        foreach ($plugin->getVersions() as $v) {
-            echo '<span class="badge">'.$v->getVersion().'</span> &nbsp;';
-        }    
-        
-    echo '</p>
-    <p>Author: <b>'.$plugin->getAuthor()->getName().'</b></p>
-    <p>License: <b>'.$plugin->getLicense().'</b></p>
-    <p>Homepage: <a href="'.$plugin->getHomepage().'">'.$plugin->getHomepage().'</a></p>
+    <table role="presentation">
+    <tr><td style="padding-right: 1ex">GroupId: </td><td><b>'.$plugin->getGroupId().'</b></td></tr>
+    <tr><td style="padding-right: 1ex">ArtifactId: </td><td><b>'.$plugin->getArtifactId().'</b></td></tr>
+    <tr><td style="padding-right: 1ex">Author:</td><td><b>'. implode("<br />", $authors) .'</b></td></tr>
+    <tr><td style="padding-right: 1ex">License:</td><td><b>'.$plugin->getLicense().'</b></td></tr>
+    <tr><td style="padding-right: 1ex">Homepage:</td><td><a href="'.$plugin->getHomepage().'">'.$plugin->getHomepage().'</a></td></tr>
+    </table>
     <p>
         <i class="fas fa-asterisk"></i> '.$plugin->getAddedAt()->format('Y-m-d').' &nbsp; &nbsp;
         <i class="fas fa-edit"></i> '.$plugin->getLastUpdatedAt()->format('Y-m-d').' &nbsp; &nbsp;
diff --git a/pp3/module/Application/view/application/index/index.phtml b/pp3/module/Application/view/application/index/index.phtml
index 288b79e..a65f891 100755
--- a/pp3/module/Application/view/application/index/index.phtml
+++ b/pp3/module/Application/view/application/index/index.phtml
@@ -106,6 +106,12 @@
   <tbody>
 <?php
     foreach($this->paginator as $plugin) {
+        /* @var $plugin Application\Entity\Plugin */
+        $authors = [];
+        foreach($plugin->getAuthors() as $author) {
+            $authors[] = htmlspecialchars($author->getName(), ENT_COMPAT, "UTF-8");
+        }
+
         $versionBadges = array();
         foreach ($plugin->getVersions() as $version) {
           foreach ($version->getNbVersionsPluginVersions() as $nbvPv) {
@@ -119,10 +125,13 @@
         echo '<tr>
         <td>
             <h4 class="text-primary"><a href="'.$this->url('catalogue', array(), array('query' => array('id'=>$plugin->getId()))).'">'.$plugin->getName().'</a></h4>
-            <p>ArtifactId: <b>'.$plugin->getArtifactId().'</b> &nbsp; &nbsp; <i class="fas fa-download"></i> '.number_format($plugin->getDownloads()).' <p>
-            <p>Author: <b>'.$plugin->getAuthor()->getName().'</b></p>
-            <p>License: <b>'.$plugin->getLicense().'</b></p> 
-            <p>'.implode('&nbsp;', $versionBadges).'</p>   
+            <table role="presentation">
+            <tr><td style="padding-right: 1ex">GroupId: </td><td><b>'.$plugin->getGroupId().'</b></td></tr>
+            <tr style="padding-right: 1ex"><td>ArtifactId:</td><td><b>'.$plugin->getArtifactId().'</b> &nbsp; &nbsp; <i class="fas fa-download"></i> '.number_format($plugin->getDownloads()).'</td></tr>
+            <tr style="padding-right: 1ex"><td>Author:</td><td><b>'. implode("<br />", $authors) .'</b></td></tr>
+            <tr style="padding-right: 1ex"><td>License:</td><td><b>'.$plugin->getLicense().'</b></td></tr>
+            </table>
+            <p>'.implode('&nbsp;', $versionBadges).'</p>
         </td>
         <td>';
 
diff --git a/pp3/module/Application/view/application/plugin/_plugin-listrow.phtml b/pp3/module/Application/view/application/plugin/_plugin-listrow.phtml
index fbaf264..ed4179a 100644
--- a/pp3/module/Application/view/application/plugin/_plugin-listrow.phtml
+++ b/pp3/module/Application/view/application/plugin/_plugin-listrow.phtml
@@ -35,15 +35,17 @@
             <i class="fas fa-sync"></i>
         </a>                
     </p>
-    <p>Status: <b>'.$plugin->getStatusTitle().'</b></p>
-    <div>
-        <p>ArtifactId: <b>'.$plugin->getArtifactId().'</b><p>
+    <table role="presentation">
+        <tr><td style="padding-right: 1ex">Status: </td><td><b>'.$plugin->getStatusTitle().'</b></td></tr>
+        <tr><td style="padding-right: 1ex">GroupId: </td><td><b>'.$plugin->getGroupId().'</b></td></tr>
+        <tr><td style="padding-right: 1ex">ArtifactId: </td><td><b>'.$plugin->getArtifactId().'</b></td></tr>
+    </table>
+        
         <p>
         <i class="fas fa-asterisk"></i> '.$plugin->getAddedAt()->format('Y-m-d').' &nbsp; &nbsp;
         <i class="fas fa-edit"></i> '.$plugin->getLastUpdatedAt()->format('Y-m-d').' &nbsp; &nbsp; 
         <i class="fas fa-download"></i> '.number_format($plugin->getDownloads()).' 
         </p>                
-    </div>
     <p>';
 
 foreach ($plugin->getCategories() as $cat) {
diff --git a/pp3/module/Application/view/application/plugin/edit.phtml b/pp3/module/Application/view/application/plugin/edit.phtml
index 7032fd6..abeeedd 100644
--- a/pp3/module/Application/view/application/plugin/edit.phtml
+++ b/pp3/module/Application/view/application/plugin/edit.phtml
@@ -19,14 +19,51 @@
  */
 ?>
 <h3>Edit Plugin</h3>
-    <div class="row">
-        <div class="col col-sm-6">
-            <?= $this->partial('layout/flash.phtml'); ?>
-            <form method="post" action="" class="needs-validation" enctype="multipart/form-data">
-            <?= $this->partial('application/plugin/_plugin-form.phtml', array('plugin' => $this->plugin, 'categories' => $this->categories)); ?>
-            <a class="btn btn-secondary" href="../plugin/list" role="button">Return</a>
-            <button type="submit" class="btn btn-primary">Save Plugin</button>
-            </form>
-            
+<?= $this->partial('layout/flash.phtml'); ?>
+<div class="">
+    <ul class="nav nav-tabs" role="tablist">
+        <li role="presentation" class="<?= $this->activeTab == 'settings' ? 'active' : '' ?>"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Plugin Settings</a></li>
+        <li role="presentation" class="<?= $this->activeTab == 'authors' ? 'active' : '' ?>"><a href="#authors" aria-controls="authors" role="tab" data-toggle="tab">Authors</a></li>
+    </ul>
+    <div class="tab-content">
+        <div class="tab-pane panel panel-default <?= $this->activeTab == 'settings' ? 'active' : '' ?>" style="padding: 2ex 1ex" role="tabpanel" id="settings">
+            <div class="row">
+                <div class="col col-sm-6">
+                    <form method="post" action="" class="needs-validation" enctype="multipart/form-data">
+                        <?= $this->partial('application/plugin/_plugin-form.phtml', array('plugin' => $this->plugin, 'categories' => $this->categories)); ?>
+                        <a class="btn btn-secondary" href="../plugin/list" role="button">Return</a>
+                        <button type="submit" class="btn btn-primary">Save Plugin</button>
+                    </form>
+                </div>
+            </div>
         </div>
-    </div>
\ No newline at end of file
+        <div class="tab-pane panel panel-default <?= $this->activeTab == 'authors' ? 'active' : '' ?>" style="padding: 2ex 1ex; position: relative" role="tabpanel" id="authors">
+            <div class="row">
+                <div class="col col-sm-6">
+                    <h4>List of authors</h4>
+                    <table role="presentation">
+                        <?php
+                        foreach ($this->plugin->getAuthors() as $author) {
+                            printf('<tr><td>%s (%s, %d)</td><td><form method="post" action="" enctype="multipart/form-data" style="display: inline-block; margin-left: 1ex"><button class="btn btn-default" role="button" name="removeAuthor" value="%d"><i class="fas fa-trash text-danger"></i></button></a></form></td></tr>',
+                                    htmlspecialchars($author->getName(), ENT_COMPAT, 'UTF-8'),
+                                    htmlspecialchars($author->getEmail(), ENT_COMPAT, 'UTF-8'),
+                                    $author->getId(),
+                                    $author->getId()
+                            );
+                        }
+                        ?>
+                    </table>
+                    <h4>Add author</h4>
+                    <form method="post" action="" enctype="multipart/form-data" style="display: inline">
+                        <div class="form-group">
+                            <label for="urgroupIdl">E-mail</label>
+                            <input type="text" class="form-control" id="addUserByEMail" name="addUserByEMail" placeholder="Please enter e-mail" value="" >
+                        </div>
+                        <a class="btn btn-secondary" href="../plugin/list" role="button">Return</a>
+                        <button type="submit" class="btn btn-primary">Add author</button>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file