diff --git a/plugin/toplinks/admin.php b/plugin/toplinks/admin.php
new file mode 100644
index 0000000000..f77f33b2c1
--- /dev/null
+++ b/plugin/toplinks/admin.php
@@ -0,0 +1,178 @@
+getRepository(TopLink::class);
+
+$pageActions = Display::url(
+ Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
+ $pageBaseUrl
+);
+$pageContent = '';
+
+$interbreadcrumb[] = [
+ 'name' => get_lang('Administration'),
+ 'url' => api_get_path(WEB_CODE_PATH).'admin/index.php',
+];
+$interbreadcrumb[] = ['name' => $plugin->get_title(), 'url' => $pageBaseUrl];
+
+switch ($httpRequest->query->getAlpha('action', 'list')) {
+ case 'list':
+ default:
+ array_pop($interbreadcrumb); // Only show link to administration in breadcrumb
+
+ $pageActions = Display::url(
+ Display::return_icon('add.png', get_lang('AddLink'), [], ICON_SIZE_MEDIUM),
+ "$pageBaseUrl?".http_build_query(['action' => 'add'])
+ );
+
+ $table = new SortableTable(
+ 'toplinks',
+ function () use ($linkRepo) {
+ return $linkRepo->count([]);
+ },
+ function ($offset, $limit, $column, $direction) use ($linkRepo) {
+ $links = $linkRepo->all($offset, $limit, $column, $direction);
+
+ return array_map(
+ function (TopLink $link) {
+ return [
+ [$link->getTitle(), $link->getUrl()],
+ $link->getId(),
+ ];
+ },
+ $links
+ );
+ },
+ 0
+ );
+ $table->set_header(0, get_lang('LinkName'));
+ $table->set_header(1, get_lang('Actions'), false, ['class' => 'th-head text-right'], ['class' => 'text-right']);
+ $table->set_column_filter(
+ 0,
+ function (array $params) {
+ [$title, $url] = $params;
+
+ return "$title
".Display::url($url, $url);
+ }
+ );
+ $table->set_column_filter(
+ 1,
+ function (int $id) use ($pageBaseUrl) {
+ return Display::url(
+ Display::return_icon('edit.png', get_lang('Edit')),
+ "$pageBaseUrl?".http_build_query(['action' => 'edit', 'link' => $id])
+ )
+ .PHP_EOL
+ .Display::url(
+ Display::return_icon('delete.png', get_lang('Delete')),
+ "$pageBaseUrl?".http_build_query(['action' => 'delete', 'link' => $id])
+ );
+ }
+ );
+
+ $pageContent = $table->return_table();
+ break;
+ case 'add':
+ $form = new TopLinkForm();
+ $form->createElements();
+
+ if ($form->validate()) {
+ $values = $form->exportValues();
+
+ $link = new TopLink();
+ $link
+ ->setTitle($values['title'])
+ ->setUrl($values['url'])
+ ->setTarget($values['target']);
+
+ $em->persist($link);
+ $em->flush();
+
+ Display::addFlash(
+ Display::return_message(get_lang('LinkAdded'), 'success')
+ );
+
+ header("Location: $pageBaseUrl");
+ exit;
+ }
+
+ $pageContent = $form->returnForm();
+ break;
+ case 'edit':
+ $link = $em->find(TopLink::class, $httpRequest->query->getInt('link'));
+
+ if (null === $link) {
+ Display::addFlash(
+ Display::return_message(get_lang('NotFound'), 'error')
+ );
+
+ header("Location: $pageBaseUrl");
+ exit;
+ }
+
+ $form = new TopLinkForm($link);
+ $form->createElements();
+
+ if ($form->validate()) {
+ $values = $form->exportValues();
+
+ $link
+ ->setTitle($values['title'])
+ ->setUrl($values['url'])
+ ->setTarget($values['target']);
+
+ $em->persist($link);
+ $em->flush();
+
+ Display::addFlash(
+ Display::return_message(get_lang('LinkModded'), 'success')
+ );
+
+ header("Location: $pageBaseUrl");
+ exit;
+ }
+
+ $form->setDefaults();
+
+ $pageContent = $form->returnForm();
+ break;
+ case 'delete':
+ $link = $em->find(TopLink::class, $httpRequest->query->getInt('link'));
+
+ if (null === $link) {
+ Display::addFlash(
+ Display::return_message(get_lang('NotFound'), 'error')
+ );
+
+ header("Location: $pageBaseUrl");
+ exit;
+ }
+
+ $em->remove($link);
+ $em->flush();
+
+ Display::addFlash(
+ Display::return_message(get_lang('LinkDeleted'), 'success')
+ );
+
+ header("Location: $pageBaseUrl");
+ exit;
+}
+
+$view = new Template($plugin->get_title());
+$view->assign('actions', Display::toolbarAction('xapi_actions', [$pageActions]));
+$view->assign('content', $pageContent);
+$view->display_one_col_template();
diff --git a/plugin/toplinks/index.php b/plugin/toplinks/index.php
new file mode 100644
index 0000000000..a4abe2dafc
--- /dev/null
+++ b/plugin/toplinks/index.php
@@ -0,0 +1,2 @@
+get_info();
diff --git a/plugin/toplinks/src/Entity/Repository/TopLinkRepository.php b/plugin/toplinks/src/Entity/Repository/TopLinkRepository.php
new file mode 100644
index 0000000000..e9ddf73a30
--- /dev/null
+++ b/plugin/toplinks/src/Entity/Repository/TopLinkRepository.php
@@ -0,0 +1,26 @@
+ $direction];
+
+ if (1 == $column) {
+ $orderBy = ['url' => $direction];
+ }
+
+ return parent::findBy([], $orderBy, $limit, $offset);
+ }
+}
diff --git a/plugin/toplinks/src/Entity/TopLink.php b/plugin/toplinks/src/Entity/TopLink.php
new file mode 100644
index 0000000000..3645fbc877
--- /dev/null
+++ b/plugin/toplinks/src/Entity/TopLink.php
@@ -0,0 +1,145 @@
+target = '_blank';
+ $this->icon = null;
+ }
+
+ /**
+ * @return int
+ */
+ public function getId(): int
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle(): string
+ {
+ return $this->title;
+ }
+
+ /**
+ * @param string $title
+ *
+ * @return TopLink
+ */
+ public function setTitle(string $title): TopLink
+ {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUrl(): string
+ {
+ return $this->url;
+ }
+
+ /**
+ * @param string $url
+ *
+ * @return TopLink
+ */
+ public function setUrl(string $url): TopLink
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTarget(): string
+ {
+ return $this->target;
+ }
+
+ /**
+ * @param string $target
+ *
+ * @return TopLink
+ */
+ public function setTarget(string $target): TopLink
+ {
+ $this->target = $target;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIcon(): string
+ {
+ return $this->icon;
+ }
+
+ /**
+ * @param string $icon
+ *
+ * @return TopLink
+ */
+ public function setIcon(string $icon): TopLink
+ {
+ $this->icon = $icon;
+
+ return $this;
+ }
+}
diff --git a/plugin/toplinks/src/LinkForm.php b/plugin/toplinks/src/LinkForm.php
new file mode 100644
index 0000000000..bf694d301d
--- /dev/null
+++ b/plugin/toplinks/src/LinkForm.php
@@ -0,0 +1,96 @@
+link = $link;
+
+ $actionParams = [
+ 'action' => 'add',
+ 'sec_token' => Security::get_existing_token(),
+ ];
+
+ if ($this->link) {
+ $actionParams['action'] = 'edit';
+ $actionParams['link'] = $this->link->getId();
+ }
+
+ $action = api_get_self().'?'.http_build_query($actionParams);
+
+ parent::__construct('frm_link', 'post', $action, '');
+ }
+
+ public function validate(): bool
+ {
+ return parent::validate() && Security::check_token('get');
+ }
+
+ public function exportValues($elementList = null)
+ {
+ Security::clear_token();
+
+ return parent::exportValues($elementList);
+ }
+
+ public function createElements()
+ {
+ if ($this->link) {
+ $this->addHeader(get_lang('LinkMod'));
+ } else {
+ $this->addHeader(get_lang('LinkAdd'));
+ }
+
+ $this->addText('title', get_lang('LinkName'));
+ $this->addUrl('url', 'URL');
+ $this->addRule('url', get_lang('GiveURL'), 'url');
+ $this->addSelect(
+ 'target',
+ [
+ get_lang('LinkTarget'),
+ get_lang('AddTargetOfLinkOnHomepage'),
+ ],
+ [
+ '_blank' => get_lang('LinkOpenBlank'),
+ '_self' => get_lang('LinkOpenSelf'),
+ ]
+ );
+ $this->addButtonSave(get_lang('SaveLink'), 'submitLink');
+ }
+
+ public function setDefaults($defaultValues = null, $filter = null)
+ {
+ $defaults = [];
+
+ if ($this->link) {
+ $defaults['title'] = $this->link->getTitle();
+ $defaults['url'] = $this->link->getUrl();
+ $defaults['target'] = $this->link->getTarget();
+ }
+
+ parent::setDefaults($defaults, null);
+ }
+}
diff --git a/plugin/toplinks/src/TopLinksPlugin.php b/plugin/toplinks/src/TopLinksPlugin.php
new file mode 100644
index 0000000000..23aee044de
--- /dev/null
+++ b/plugin/toplinks/src/TopLinksPlugin.php
@@ -0,0 +1,45 @@
+ 'boolean',
+ ];
+
+ parent::__construct(
+ '0.1',
+ 'Angel Fernando Quiroz Campos ',
+ $settings
+ );
+ }
+
+ /**
+ * @return \TopLinksPlugin
+ */
+ public static function create(): TopLinksPlugin
+ {
+ static $result = null;
+
+ return $result ? $result : $result = new self();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAdminUrl()
+ {
+ $webPath = api_get_path(WEB_PLUGIN_PATH).$this->get_name();
+
+ return "$webPath/admin.php";
+ }
+}