فهرست منبع

david solution

internship 2 سال پیش
والد
کامیت
ee0cd76dd3
30فایلهای تغییر یافته به همراه1890 افزوده شده و 322 حذف شده
  1. 0 1
      .idea/internship-playground.iml
  2. 9 9
      composer.json
  3. 180 300
      composer.lock
  4. 110 0
      src-php/app/internship/bo/NewsCategory.php
  5. 131 0
      src-php/app/internship/bo/NewsItem.php
  6. 65 0
      src-php/app/internship/bo/NewsTag.php
  7. 76 5
      src-php/app/internship/controller/ArticleController.php
  8. 93 0
      src-php/app/internship/controller/ArticleControllerDavid.php
  9. 189 0
      src-php/app/internship/controller/NewsController.php
  10. 13 2
      src-php/app/internship/model/ArticleDao.php
  11. 84 0
      src-php/app/internship/model/CategoryForm.php
  12. 73 0
      src-php/app/internship/model/NewsDao.php
  13. 135 0
      src-php/app/internship/model/NewsForm.php
  14. 60 0
      src-php/app/internship/model/TagForm.php
  15. 21 0
      src-php/app/internship/model/TestModel.php
  16. 38 0
      src-php/app/internship/view/boilerplate.html.php
  17. 39 0
      src-php/app/internship/view/categorydetail.html.php
  18. 43 0
      src-php/app/internship/view/createcategory.html.php
  19. 54 0
      src-php/app/internship/view/createnews.html.php
  20. 36 0
      src-php/app/internship/view/createtag.html.php
  21. 37 0
      src-php/app/internship/view/deletecategory.html.php
  22. 48 0
      src-php/app/internship/view/deletenews.html.php
  23. 25 0
      src-php/app/internship/view/deletetag.html.php
  24. 30 0
      src-php/app/internship/view/newsdetail.html.php
  25. 35 0
      src-php/app/internship/view/overview.html.php
  26. 40 0
      src-php/app/internship/view/taglist.html.php
  27. 109 0
      src-php/atusch.php
  28. 108 3
      src-php/test/internship/controller/ArticleControllerTest.php
  29. 2 1
      src-php/var/etc/.gitignore
  30. 7 1
      src-php/var/etc/internship/app.ini

+ 0 - 1
.idea/internship-playground.iml

@@ -6,7 +6,6 @@
       <sourceFolder url="file://$MODULE_DIR$/src-php/app/internship" isTestSource="false" packagePrefix="custom\" />
       <sourceFolder url="file://$MODULE_DIR$/src-php/test/internship" isTestSource="true" packagePrefix="custom\" />
       <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src-php/app" isTestSource="false" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/myclabs/deep-copy" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/nikic/php-parser" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/composer" />

+ 9 - 9
composer.json

@@ -5,18 +5,18 @@
     "minimum-stability": "dev",
     "prefer-stable": true,
     "require": {
-        "n2n/n2n": "^7.3",
-        "n2n/n2n-batch": "^7.3",
-        "n2n/n2n-context": "^7.3",
-        "n2n/n2n-mail": "^7.3",
-        "n2n/n2n-impl-persistence-meta": "^7.3",
-        "n2n/n2n-impl-persistence-orm": "^7.3",
-        "n2n/n2n-impl-web-dispatch": "^7.3",
-        "n2n/n2n-impl-web-ui": "^7.3"
+        "n2n/n2n": "^7.4",
+        "n2n/n2n-batch": "^7.4",
+        "n2n/n2n-context": "^7.4",
+        "n2n/n2n-mail": "^7.4",
+        "n2n/n2n-impl-persistence-meta": "^7.4",
+        "n2n/n2n-impl-persistence-orm": "^7.4",
+        "n2n/n2n-impl-web-dispatch": "^7.4",
+        "n2n/n2n-impl-web-ui": "^7.4"
     },
     "require-dev": {
         "n2n/hangar": "^7.2.0",
-        "n2n/n2n-test" : "^7.3",
+        "n2n/n2n-test" : "^7.4",
         "phpunit/phpunit" : "^9.5"
     },
     "autoload" : {

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 180 - 300
composer.lock


+ 110 - 0
src-php/app/internship/bo/NewsCategory.php

@@ -0,0 +1,110 @@
+<?php
+namespace internship\bo;
+
+use n2n\reflection\ObjectAdapter;
+use n2n\reflection\annotation\AnnoInit;
+use n2n\persistence\orm\annotation\AnnoOneToMany;
+use n2n\persistence\orm\CascadeType;
+use internship\bo\NewsItem;
+
+class NewsCategory extends ObjectAdapter {
+	private static function _annos(AnnoInit $ai) {
+		$ai->p('newsItems', new AnnoOneToMany(NewsItem::getClass(), 'category', CascadeType::ALL));
+	}
+	private int $id;
+	private string $title;
+	private string $lead;
+	private string $content;
+	private ?string $imageFile;
+	private string $urlPart;
+	private $newsItems;
+
+	/**
+	 * @return int
+	 */
+	public function getId(): int {
+		return $this->id;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getTitle(): string {
+		return $this->title;
+	}
+
+	/**
+	 * @param string $title
+	 */
+	public function setTitle(string $title): void {
+		$this->title = $title;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getContent(): string {
+		return $this->content;
+	}
+
+	/**
+	 * @param string $content
+	 */
+	public function setContent(string $content): void {
+		$this->content = $content;
+	}
+	/**
+	 * @return string
+	 */
+	public function getLead(): string {
+		return $this->lead;
+	}
+
+	/**
+	 * @param string $lead
+	 */
+	public function setLead(string $lead): void {
+		$this->lead = $lead;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getImageFile(): string {
+		return $this->imageFile;
+	}
+
+	/**
+	 * @param string $imageFile
+	 */
+	public function setImageFile(?string $imageFile): void {
+		$this->imageFile = $imageFile;
+	}
+	/**
+	 * @return string
+	 */
+	public function getUrlPart() {
+		return $this->urlPart;
+	}
+	/**
+	 * @param string $urlPart
+	 */
+	public function setUrlPart($urlPart) {
+		$this->urlPart = $urlPart;
+	}
+
+	/**
+	 * @return mixed
+	 */
+	public function getNewsItems() {
+		return $this->newsItems;
+	}
+
+	/**
+	 * @param mixed $newsItems
+	 */
+	public function setNewsItems(\ArrayObject $newsItems): void {
+		$this->newsItems = $newsItems;
+	}
+
+}

+ 131 - 0
src-php/app/internship/bo/NewsItem.php

@@ -0,0 +1,131 @@
+<?php
+namespace internship\bo;
+
+use n2n\reflection\ObjectAdapter;
+use internship\bo\NewsCategory;
+use n2n\reflection\annotation\AnnoInit;
+use n2n\persistence\orm\annotation\AnnoManyToOne;
+use n2n\persistence\orm\annotation\AnnoManyToMany;
+use n2n\persistence\orm\annotation\AnnoManagedFile;
+use n2n\persistence\orm\CascadeType;
+
+class NewsItem extends ObjectAdapter {
+	private static function _annos(AnnoInit $ai) {
+		$ai->p('category', new AnnoManyToOne(NewsCategory::getClass(), CascadeType::MERGE|CascadeType::PERSIST));
+		$ai->p('tags', new AnnoManyToMany(NewsTag::getClass()));
+
+		//$ai->p('imageFile', new AnnoManagedFile());
+	}
+	private int $id;
+	private string $title;
+	private string $lead;
+	private string $content;
+	private ?string $imageFile;
+	private string $urlPart;
+	private NewsCategory $category;
+	private \ArrayObject $tags;
+
+	function __construct() {
+		$this->tags = new \ArrayObject();
+	}
+
+	/**
+	 * @return int
+	 */
+	public function getId(): int {
+		return $this->id;
+	}
+	/**
+	 * @return NewsCategory
+	 */
+	public function getCategory(): NewsCategory {
+		return $this->category;
+	}
+	/**
+	 * @param NewsCategory $category
+	 */
+	public function setCategory($category): void {
+		$this->category = $category;
+	}
+	/**
+	 * @return \ArrayObject
+	 */
+	public function getTags(): \ArrayObject {
+		return $this->tags;
+	}
+	/**
+	 * @param \ArrayObject $tags
+	 */
+	public function setTags(\ArrayObject $tags): void {
+		$this->tags = $tags;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getTitle(): string {
+		return $this->title;
+	}
+
+	/**
+	 * @param string $title
+	 */
+	public function setTitle(string $title): void {
+		$this->title = $title;
+	}
+	/**
+	 * @return string
+	 */
+	public function getLead(): string {
+		return $this->lead;
+	}
+
+	/**
+	 * @param string $lead
+	 */
+	public function setLead(string $lead): void {
+		$this->lead = $lead;
+	}
+	/**
+	 * @return string
+	 */
+	public function getContent(): string {
+		return $this->content;
+	}
+
+	/**
+	 * @param string $content
+	 */
+	public function setContent(string $content): void {
+		$this->content = $content;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getImageFile(): string {
+		return $this->imageFile;
+	}
+
+	/**
+	 * @param string $imageFile
+	 */
+	public function setImageFile(?string $imageFile): void {
+		$this->imageFile = $imageFile;
+	}
+	/**
+	 * @return string
+	 */
+	public function getUrlPart() {
+		return $this->urlPart;
+	}
+	/**
+	 * @param string $urlPart
+	 */
+	public function setUrlPart($urlPart) {
+		$this->urlPart = $urlPart;
+	}
+	public function equals(NewsItem $newsItem) {
+		return $this->id == $newsItem->getId();
+	}
+}

+ 65 - 0
src-php/app/internship/bo/NewsTag.php

@@ -0,0 +1,65 @@
+<?php
+namespace internship\bo;
+
+use n2n\reflection\ObjectAdapter;
+use internship\bo\NewsItem;
+use n2n\reflection\annotation\AnnoInit;
+use n2n\persistence\orm\annotation\AnnoManyToMany;
+use n2n\persistence\orm\annotation\AnnoManagedFile;
+use n2n\persistence\orm\CascadeType;
+
+class NewsTag extends ObjectAdapter {
+	private static function _annos(AnnoInit $ai) {
+		$ai->p('newsItems', new AnnoManyToMany(NewsItem::getClass(), 'tags'));
+	}
+	private \ArrayObject $newsItems;
+	private int $id;
+	private string $name;
+
+
+	/**
+	 * @return int
+	 */
+	public function getId() {
+		return $this->id;
+	}
+
+	/**
+	 * @param int $id
+	 */
+	public function setId($id): void {
+		$this->id = $id;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getName() {
+		return $this->name;
+	}
+
+	/**
+	 * @param string $name
+	 */
+	public function setName($name): void {
+		$this->name = $name;
+	}
+	public function equals(NewsTag $newsTag) {
+		return $this->id == $newsTag->getId();
+	}
+
+	/**
+	 * @return mixed
+	 */
+	public function getNewsItems() {
+		return $this->newsItems;
+	}
+
+	/**
+	 * @param mixed $newsItems
+	 */
+	public function setNewsItems(\ArrayObject $newsItems): void {
+		$this->newsItems = $newsItems;
+	}
+
+}

+ 76 - 5
src-php/app/internship/controller/ArticleController.php

@@ -7,6 +7,7 @@ use n2n\context\attribute\Inject;
 use n2n\web\http\controller\ParamBody;
 use internship\bo\Article;
 use n2n\web\http\PageNotFoundException;
+use n2n\web\http\BadRequestException;
 
 /**
  * REST Controller
@@ -16,6 +17,13 @@ class ArticleController extends ControllerAdapter {
 	#[Inject]
 	private ArticleDao $articleDao;
 
+	public function index() {
+		echo 'hallo atusch';
+	}
+
+
+
+
 	/**
 	 * Gibt den {@see Article} mit der entsprechenden id im JSON Format zurück.
 	 *
@@ -44,7 +52,11 @@ class ArticleController extends ControllerAdapter {
 	 * @throws PageNotFoundException if Article could not be found.
 	 */
 	function getDoArticle(int $articleId): void {
-
+		$article = $this->articleDao->getArticleById($articleId);
+		if ($article === null) {
+			throw new PageNotFoundException('No Article with this id');
+		}
+		$this->sendJson($article);
 	}
 
 	/**
@@ -67,6 +79,17 @@ class ArticleController extends ControllerAdapter {
 	 * @return void
 	 */
 	function getDoArticles(string $categoryName = null): void {
+		if ($categoryName === null) {
+			$articles = $this->articleDao->getArticles();
+		} else {
+			$articles = $this->articleDao->getArticlesByCategoryName($categoryName);
+		}
+
+
+//		if (empty($articles)) {
+//			throw new PageNotFoundException('No such category // no Categories');
+//		}
+		$this->sendJson($articles);
 	}
 
 	/**
@@ -81,7 +104,7 @@ class ArticleController extends ControllerAdapter {
 	 *		Mache eine neue Entity {@see Article} und befülle sie mit den übergebenen Daten.
 	 * 	</li>
 	 *  <li>
-	 *		Implementiere eine neue Methode im {@see ArticleDao} und bennene sie "saveArticle".
+	 *		Implementiere eine neue Methode im {@see ArticleDao} und benenne sie "saveArticle".
 	 * 	 </li>
 	 * </ul>
 	 *
@@ -89,8 +112,29 @@ class ArticleController extends ControllerAdapter {
 	 *
 	 * @return void
 	 */
-	function postDoArticle(ParamBody $body): void {
-		$body->parseJson();
+	function postDoNewArticle(ParamBody $body): void {
+		$articleData = $body->parseJson();
+		if (!array_key_exists('title', $articleData)
+				|| !array_key_exists('text', $articleData)
+				|| !array_key_exists('categoryName', $articleData)) {
+			throw new BadRequestException('param: title, text, or categoryName is missing');
+		}
+		if (empty($articleData['title']) || empty($articleData['text']) || empty($articleData['categoryName'])) {
+			throw new BadRequestException('title, text, or categoryName is empty');
+		}
+		if (!in_array($articleData['categoryName'], ['international', 'national', 'sport'])) {
+			throw new BadRequestException('invalid category name');
+		}
+		$article = new Article();
+		$article->setTitle((substr_replace($articleData['title'], "", 255)));
+		$article->setText($articleData['text']);
+		$article->setCategoryName($articleData['categoryName']);
+
+		$this->beginTransaction();
+		$this->articleDao->saveArticle($article);
+		$this->commit();
+		$this->sendJson($article);
+
 	}
 
 	/**
@@ -112,7 +156,28 @@ class ArticleController extends ControllerAdapter {
 	 * @return void
 	 */
 	function putDoArticle(int $articleId, ParamBody $body): void {
+		$articleObject = $this->articleDao->getArticleById($articleId);
+		if (empty($articleObject)) {
+			throw new PageNotFoundException('invalid article ID');
+		}
+		$article = $body->parseJson();
+		if (array_key_exists('title', $article) && !empty($article['title'])) {
+			$articleObject->setTitle((substr_replace($article['title'], "", 255)));
+		}
+		if (array_key_exists('text', $article) && !empty($article['text'])) {
+			$articleObject->setText($article['text']);
+		}
+		if (array_key_exists('categoryName', $article) && !empty($article['categoryName'])) {
+			if (!in_array($article['categoryName'], ['international', 'national', 'sport'])) {
+				throw new BadRequestException('invalid category name');
+			}
+			$articleObject->setCategoryName($article['categoryName']);
+		}
 
+		$this->beginTransaction();
+		$this->articleDao->saveArticle($articleObject);
+		$this->commit();
+		$this->sendJson($articleObject->jsonSerialize());
 	}
 
 	/**
@@ -125,6 +190,12 @@ class ArticleController extends ControllerAdapter {
 	 * @return void
 	 */
 	function deleteDoArticle(int $articleId): void {
-
+		$articleObject = $this->articleDao->getArticleById($articleId);
+		if (empty($articleObject)) {
+			throw new PageNotFoundException('invalid article ID');
+		}
+		$this->beginTransaction();
+		$this->articleDao->removeArticle($articleObject);
+		$this->commit();
 	}
 }

+ 93 - 0
src-php/app/internship/controller/ArticleControllerDavid.php

@@ -0,0 +1,93 @@
+<?php
+namespace internship\controller;
+
+use n2n\web\http\controller\ControllerAdapter;
+use n2n\web\http\controller\ParamPath;
+use n2n\web\http\controller\ParamQuery;
+use internship\model\ArticleDao;
+use n2n\context\attribute\Inject;
+use n2n\web\http\controller\ParamBody;
+use internship\bo\Article;
+use n2n\web\http\PageNotFoundException;
+use n2n\reflection\annotation\AnnoInit;
+use n2n\web\http\annotation\AnnoPath;
+use n2n\web\http\BadRequestException;
+use custom\model\TestModel;
+
+/**
+ * REST Controller
+ * https://dev.n2n.rocks/de/n2n/docs/rest
+ **/
+
+class ArticleControllerDavid extends ControllerAdapter {
+	private static function _annos(AnnoInit $ai) {
+		$ai->m('anno', new AnnoPath('param1:*/const/param2:*'));
+		$ai->m('holeradio', new AnnoPath ('param1:#^[a-z3-9]+$#/holeradio/params+:#^[1-9]?[0-9]$#'));
+	}
+	#[Inject]
+	private ArticleDao $articleDao;
+	public function anno($param1, $param2) {
+		echo 'anno controller function David <br/>';
+		echo 'param1: ' . $param1 . ' param2: ' . $param2;
+	}
+	public function holeradio($param1, array $params) {
+		if (array_sum($params) > '999') {
+			throw new BadRequestException('sum bigger than 999');
+		}
+		echo 'param1: ' . $param1 . '<br/>';
+		echo 'params: ' . implode(', ', $params) .' sum: '. array_sum($params);
+	}
+	public function index(ParamQuery $param1 = null, ParamQuery $param2 = null) {
+		echo 'Index controller function David <br/>';
+		echo 'param1: ' . $param1 . ' param2: ' . $param2;
+	}
+	public function doDetail(ParamPath $param1 = null, ParamPath $param2 = null, ParamPath $param3 = null) {
+		echo 'doDetail controller function David <br/>';
+		echo 'param1: ' . $param1 . ' param2: ' . $param2 . ' param3: ' . $param3;
+	}
+	public function doAdvancedDetail(ParamQuery $param1 = null, ParamQuery $param2 = null, ParamQuery $param3 = null) {
+		echo 'doAdvancedDetail controller function David <br/>';
+		echo 'param1: ' . $param1 . ' param2: ' . $param2 . ' param3: ' . $param3;
+	}
+	public function doHallo(ParamPath $test3 = null, ParamQuery $param1 = null, ParamQuery $param2 = null) {
+		echo 'doHallo controller function David <br/>';
+		if ($test3 !== null) {
+			if ($test3?->toString() !== 'test3') {
+				throw new PageNotFoundException('not test3');
+			}
+			echo 'ParamPath = '. $test3. '<br/>';
+		}
+		echo 'param1: ' . $param1 . ' param2: ' . $param2;
+	}
+
+	#[Inject]
+	private TestModel $testModel;
+
+	public function doHuii(DavidDelegateController $davidDelegateController,
+			$param, array $delegateParams = null) {
+		echo 'Test 1: ' . $this->testModel->getTest() . '<br/>';
+		$this->delegate($davidDelegateController, 2);
+	}
+	public function doHoi(DavidDelegateController $davidDelegateController, array $delegateParams = null) {
+		echo 'Test 1: ' . $this->testModel->getTest() . '<br/>';
+		$this->testModel->setTest('value ' . uniqid());
+		$this->delegate($davidDelegateController);
+	}
+
+	function getDoArticle(int $articleId): void {
+		echo 'hallo';
+	}
+}
+
+class DavidDelegateController extends ControllerAdapter {
+	public function doBaz(TestModel $testModel, $param = null) {
+		echo 'Test 2: ' . $testModel->getTest(). '<br/>';
+		echo 'param: ' . $param;
+	}
+	public function index(array $params = null) {
+		echo 'delegate is cool';
+		if($params !== null) {
+			echo '<br/>' . 'params: ' . implode(', ', $params);
+		}
+	}
+}

+ 189 - 0
src-php/app/internship/controller/NewsController.php

@@ -0,0 +1,189 @@
+<?php
+namespace internship\controller;
+
+use n2n\web\http\controller\ControllerAdapter;
+use internship\model\NewsDao;
+use internship\model\CategoryForm;
+use n2n\reflection\annotation\AnnoInit;
+use n2n\web\http\annotation\AnnoPath;
+use internship\model\NewsForm;
+use n2n\web\http\PageNotFoundException;
+use internship\model\TagForm;
+use n2n\l10n\MessageContainer;
+use n2n\context\attribute\Inject;
+use internship\bo\NewsCategory;
+use internship\bo\NewsItem;
+use internship\bo\NewsTag;
+
+class NewsController extends ControllerAdapter {
+	private static function _annos(AnnoInit $ai) {
+			$ai->m('categoryDetail', new AnnoPath('/urlPart:*'));
+			$ai->m('newsDetail', new AnnoPath('newsCategoryUrlPart:*/detail/newsItemUrlPart:*/id:*'));
+	}
+	private NewsDao $newsDao;
+
+	private function _init(NewsDao $newsDao) {
+		$this->newsDao = $newsDao;
+	}
+	#[Inject]
+	private MessageContainer $mc;
+	public function index() {
+		$newsCategories = $this->newsDao->getNewsCategories();
+		$this->forward('~\view\overview.html', ['newsCategories' => $newsCategories, 'mc' => $this->mc]);
+	}
+	public function categoryDetail( string $urlPart) {
+		$NewsCategory  = $this->newsDao->getNewsCategory($urlPart);
+		if ($NewsCategory === null) {
+			throw new PageNotFoundException('Kategorie nicht gefunden');
+		}
+		$this->forward('~\view\categorydetail.html', ['newsCategory' => $NewsCategory, 'mc' => $this->mc]);
+	}
+	public function newsDetail(string $newsCategoryUrlPart, string $newsItemUrlPart, int $id) {
+		$newsItem  = $this->newsDao->getNewsItemById($id);
+		if ($newsItem === null) {
+			throw new PageNotFoundException('News nicht gefunden');
+		}
+		$newsCategory  = $this->newsDao->getNewsCategory($newsCategoryUrlPart);
+		if ($newsCategory === null || $newsCategory != $newsItem->getCategory()) {
+			throw new PageNotFoundException('News oder News Kategorie nicht gefunden');
+		}
+		if ($newsItem->getUrlPart() != $newsItemUrlPart) {
+			throw new PageNotFoundException('News-Url nicht gefunden');
+		}
+
+
+		$this->forward('~\view\newsdetail.html', ['newsItem' => $newsItem, 'mc' => $this->mc]);
+	}
+	public function doCreateCategory(int $id = null) {
+		$category = null;
+		$method = 'new';
+		if ($id !== null) {
+			$category = $this->newsDao->getNewsCategoryById($id);
+			if ($category instanceof NewsCategory) {
+				$method = 'edit';
+			}
+		}
+		$categoryForm = new CategoryForm($category);
+		$this->beginTransaction();
+		if ($this->dispatch($categoryForm, 'save')) {
+			$this->commit();
+			$this->mc->addInfo('Kategorie erfolgreich erstellt!');
+			$this->redirectToController();
+			return;
+		}
+		if ($this->dispatch($categoryForm, 'delete')) {
+			$this->commit();
+			$this->mc->addInfo('Kategorie erfolgreich gelöscht!');
+			$this->redirectToController();
+			return;
+		}
+		$this->commit();
+		$this->forward('~\view\createcategory.html',
+				array('categoryForm'=> $categoryForm, 'mc' => $this->mc, 'method' => $method));
+
+	}
+	public function doDeleteCategory(int $id) {
+		$category = $this->newsDao->getNewsCategoryById($id);
+		if ($category === null) {
+			throw new PageNotFoundException('invalid category id: ' . $id);
+		}
+		$this->beginTransaction();
+		$this->newsDao->deleteCategory($category);
+		$this->commit();
+		$this->mc->addInfo('Kategorie, inkl. zugeordneter News erfolgreich gelöscht!');
+		$this->redirectToController();
+
+	}
+	public function doCreateNews(string $urlPart, int $newsId = null) {
+		$category = $this->newsDao->getNewsCategory($urlPart);
+		$method = 'new';
+		$news = null;
+		if ($category === null) {
+			throw new PageNotFoundException('invalid category name: ' . $urlPart);
+		}
+		if ($newsId !== null){
+			$news = $this->newsDao->getNewsItemById($newsId);
+			if ($news instanceof NewsItem) {
+				$method = 'edit';
+			}
+		}
+		$this->beginTransaction();
+
+		$newsForm = new NewsForm($category, $this->newsDao->getTags(), $news);
+		if ($this->dispatch($newsForm, 'save')) {
+			$this->commit();
+			$this->mc->addInfo('NewsItem erfolgreich erstellt!');
+			$this->redirectToController(array($urlPart));
+			return;
+		}
+		if ($this->dispatch($newsForm, 'delete')) {
+			$this->commit();
+			$this->mc->addInfo('NewsItem erfolgreich gelöscht!');
+			$this->redirectToController(array($urlPart));
+			return;
+		}
+		$this->commit();
+		$this->forward('~\view\createnews.html',
+				array('newsForm'=> $newsForm,'urlPart'=> $urlPart, 'mc' => $this->mc, 'method' => $method));
+
+	}
+
+	public function doDeleteNews(int $newsId) {
+		$news = $this->newsDao->getNewsItemById($newsId);
+		if (empty($news)) {
+			throw new PageNotFoundException('invalid news id: ' . $newsId);
+		}
+		$this->beginTransaction();
+		$this->newsDao->deleteNews($news);
+		$this->commit();
+		$this->mc->addInfo('NewsItem erfolgreich gelöscht!');
+
+		$this->redirectToController();
+
+	}
+	public function doCreateTag(int $tagId = null) {
+		$tag = null;
+		$method = 'new';
+		if ($tagId !== null) {
+			$tag = $this->newsDao->getNewsTagById($tagId);
+			if ($tag instanceof NewsTag) {
+				$method = 'edit';
+			}
+		}
+		$this->beginTransaction();
+		$tagForm = new TagForm($tag);
+		if ($this->dispatch($tagForm, 'save')) {
+			$this->commit();
+			$this->mc->addInfo('Tag erfolgreich erstellt!');
+			$this->redirectToController();
+			return;
+		}
+		if ($this->dispatch($tagForm, 'delete')) {
+			$this->commit();
+			$this->mc->addInfo('Tag erfolgreich gelöscht!');
+			$this->redirectToController();
+			return;
+		}
+		$this->commit();
+		$this->forward('~\view\createtag.html',
+				array('tagForm'=> $tagForm, 'mc' => $this->mc, 'method' => $method));
+	}
+	public function doDeleteTag(int $tagId) {
+		$tag = $this->newsDao->getNewsTagById($tagId);
+		if (empty($tag)) {
+			throw new PageNotFoundException('invalid tag id: ' . $tagId);
+		}
+		$this->beginTransaction();
+		$this->newsDao->deleteTag($tag);
+		$this->commit();
+		$this->mc->addInfo('Tag erfolgreich gelöscht!');
+
+		$this->redirectToController();
+	}
+	public function doTagList(string $tagName = null) {
+		$newsTags = $this->newsDao->getTags($tagName);
+		$this->forward('~\view\taglist.html', array('newsTags'=> $newsTags, 'mc' => $this->mc));
+	}
+
+
+}

+ 13 - 2
src-php/app/internship/model/ArticleDao.php

@@ -24,7 +24,9 @@ class ArticleDao {
 	 * @return Article[]
 	 */
 	function getArticles(): array {
-
+		$criteria = $this->em->createSimpleCriteria(Article::getClass(), null,
+				array('id' => 'DESC'));
+		return $criteria->toQuery()->fetchArray();
 	}
 
 	/**
@@ -33,7 +35,9 @@ class ArticleDao {
 	 * @return array
 	 */
 	function getArticlesByCategoryName(string $categoryName): array {
-
+		$criteria = $this->em->createSimpleCriteria(Article::getClass(), ['categoryName' => $categoryName],
+				['id' => 'DESC']);
+		return $criteria->toQuery()->fetchArray();
 	}
 
 	/**
@@ -43,6 +47,13 @@ class ArticleDao {
 	 * @return Article|null
 	 */
 	function getArticleById(int $id): ?Article {
+		return $this->em->find(Article::getClass(), $id);
+	}
 
+	function saveArticle(Article $articleObject) {
+		$this->em->persist($articleObject);
+	}
+	function removeArticle(Article $articleObject) {
+		$this->em->remove($articleObject);
 	}
 }

+ 84 - 0
src-php/app/internship/model/CategoryForm.php

@@ -0,0 +1,84 @@
+<?php
+namespace internship\model;
+
+use n2n\web\dispatch\Dispatchable;
+use internship\bo\NewsCategory;
+use n2n\web\dispatch\map\bind\BindingDefinition;
+use n2n\impl\web\dispatch\map\val\ValNotEmpty;
+use n2n\impl\web\dispatch\map\val\ValImageFile;
+use n2n\io\managed\File;
+use n2n\web\dispatch\map\MappingResult;
+use n2n\util\io\IoUtils;
+use n2n\web\http\PageNotFoundException;
+
+class CategoryForm implements Dispatchable {
+	private NewsCategory $category;
+
+	protected string $title;
+	protected string $content;
+	protected $imageFile;
+
+	public function __construct(NewsCategory $category = null) {
+		$this->title = '';
+		$this->content = '';
+		if ($category) {
+			$this->title = $category->getTitle();
+			$this->id = $category->getId();
+			$this->content = $category->getContent();
+			$this->category = $category;
+		}
+	}
+	public function getTitle() {
+		return $this->title;
+	}
+
+	public function setTitle(string $title) {
+		$this->title = $title;
+	}
+
+	public function getImageFile() {
+		return $this->imageFile;
+	}
+
+	public function setImageFile(File $imageFile = null) {
+		$this->imageFile = $imageFile;
+	}
+
+	public function getContent() {
+		return $this->content;
+	}
+
+	public function setContent(string $content) {
+		$this->content = $content;
+	}
+
+	private function _mapping(MappingResult $mr) {
+		$mr->setLabel('title', 'Titel');
+		$mr->setLabel('content', 'Beschreibung');
+		$mr->setLabel('imageFile', 'Bild');
+	}
+
+	private function _validation(BindingDefinition $bd) {
+		$bd->val(array('content', 'title'), new ValNotEmpty());
+		$bd->val('imageFile', new ValImageFile(true));
+	}
+
+	public function save(NewsDao $newsDao) {
+		$category = $this->category ?? new NewsCategory();
+		$category->setTitle($this->title);
+		$category->setLead(substr_replace($this->content, "...", 50));
+		$category->setContent($this->content);
+		$category->setImageFile($this->imageFile);
+		$category->setUrlPart(strtolower(IoUtils::stripSpecialChars($this->title)));
+		$newsDao->saveCategory($category);
+	}
+
+	public function delete(NewsDao $newsDao) {
+		if (!$this->category) {
+			throw new PageNotFoundException('no id set');
+		}
+		$category = $this->category;
+		$newsDao->deleteCategory($category);
+	}
+
+}

+ 73 - 0
src-php/app/internship/model/NewsDao.php

@@ -0,0 +1,73 @@
+<?php
+namespace internship\model;
+
+use n2n\context\attribute\RequestScoped;
+use n2n\persistence\orm\EntityManager;
+use n2n\io\managed\File;
+use internship\bo\NewsCategory;
+use internship\bo\NewsItem;
+use internship\bo\NewsTag;
+use n2n\context\attribute\Inject;
+use n2n\util\io\IoUtils;
+
+
+#[RequestScoped]
+class NewsDao {
+	#[Inject]
+	private EntityManager $em;
+
+	public function getNewsCategories() {
+		$criteria = $this->em->createSimpleCriteria(NewsCategory::getClass(), null,
+				array('id' => 'DESC'));
+		return $criteria->toQuery()->fetchArray();
+	}
+	public function getNewsCategory(string $urlPart): ?NewsCategory {
+		$criteria = $this->em->createSimpleCriteria(NewsCategory::getClass(), array('urlPart' => $urlPart));
+		return $criteria->toQuery()->fetchSingle();
+	}
+	public function getNewsCategoryById(int $id) {
+		return $this->em->find(NewsCategory::getClass(), $id);
+	}
+	public function saveCategory(NewsCategory $category) {
+		$this->em->persist($category);
+	}
+	public function deleteCategory(NewsCategory $category) {
+		$this->em->remove($category);
+	}
+
+
+	public function getNewsItemById(int $id) {
+		return $this->em->find(NewsItem::getClass(), $id);
+	}
+	public function saveNews(NewsItem $news) {
+		$this->em->persist($news);
+	}
+	public function deleteNews(NewsItem $news) {
+		$this->em->remove($news);
+	}
+
+
+	public function getNewsTagById(int $id) {
+		return $this->em->find(NewsTag::getClass(), $id);
+	}
+	public function getNewsByTagName(string $name) {
+		$criteria = $this->em->createSimpleCriteria(NewsTag::getClass(), array('name' => $name),
+				array('name' => 'ASC'));
+		return $criteria->toQuery()->fetchArray();
+	}
+	public function getTags(string $name = null) {
+		$matches = null;
+		if($name) {$matches = array('name' => $name);}
+		$criteria = $this->em->createSimpleCriteria(NewsTag::getClass(), $matches,
+				array('name' => 'ASC'));
+		return $criteria->toQuery()->fetchArray();
+	}
+
+	public function saveTag(NewsTag $tag) {
+		$this->em->persist($tag);
+	}
+	public function deleteTag(NewsTag $tag) {
+		$this->em->remove($tag);
+	}
+
+}

+ 135 - 0
src-php/app/internship/model/NewsForm.php

@@ -0,0 +1,135 @@
+<?php
+namespace internship\model;
+
+use n2n\web\dispatch\Dispatchable;
+use internship\bo\NewsCategory;
+use internship\bo\NewsTag;
+use n2n\web\dispatch\map\bind\BindingDefinition;
+use n2n\impl\web\dispatch\map\val\ValNotEmpty;
+use n2n\impl\web\dispatch\map\val\ValImageFile;
+use n2n\io\managed\File;
+use n2n\web\dispatch\map\MappingResult;
+use n2n\util\io\IoUtils;
+use internship\bo\NewsItem;
+use n2n\web\http\PageNotFoundException;
+use n2n\util\type\ArgUtils;
+
+
+class NewsForm implements Dispatchable {
+	private ?NewsItem $news = null;
+	protected string $title;
+	protected string $content;
+	protected ?File $imageFile = null;
+	protected array $tagIds = array();
+
+	/**
+	 * @param NewsCategory $category
+	 * @param NewsTag[] $tags
+	 * @param NewsItem|null $news
+	 */
+	public function __construct(private NewsCategory $category, private array $tags, NewsItem $news = null) {
+		ArgUtils::valArray($tags, NewsTag::class);
+
+		if ($news === null) {
+			return;
+		}
+
+		$this->title = $news->getTitle();
+		$this->content = $news->getContent();
+		$this->id = $news->getId();
+		$this->tagIds = array_map(fn(NewsTag $t) => $t->getId(), $news->getTags()->getArrayCopy());
+
+		$this->news = $news;
+	}
+
+	public function getTitle(): ?string {
+		return $this->title ?? null;
+	}
+
+	/**
+	 * @param string $title
+	 */
+	public function setTitle(string $title) {
+		$this->title = $title;
+	}
+
+	public function getImageFile(): ?File {
+		return $this->imageFile;
+	}
+
+	public function setImageFile(?File $imageFile) {
+		$this->imageFile = $imageFile;
+	}
+
+	/**
+	 * @return array
+	 */
+	public function getTagIds(): array {
+		return $this->tagIds;
+	}
+
+	/**
+	 * @param array $tagIds
+	 */
+	public function setTagIds(array $tagIds): void {
+		$this->tagIds = $tagIds;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getContent(): ?string {
+		return $this->content ?? null;
+	}
+	/**
+	 * @param string $content
+	 */
+	public function setContent(string $content) {
+		$this->content = $content;
+	}
+	public function getTagOptions(): array {
+		return $this->tags;
+
+	}
+
+	private function _mapping(MappingResult $mr) {
+		$mr->setLabel('title', 'Titel');
+		$mr->setLabel('content', 'Beschreibung');
+		$mr->setLabel('imageFile', 'Bild');
+		$mr->setLabel('tagIds', 'zugewiesene Tags');
+	}
+
+	private function _validation(BindingDefinition $bd) {
+		$bd->val(array('content', 'title'), new ValNotEmpty());
+		$bd->val('imageFile', new ValImageFile(true));
+	}
+
+
+
+	public function save(NewsDao $newsDao) {
+		$selectedTagObjects = new \ArrayObject();
+		foreach ($this->tags as $tagObject) {
+			if (in_array($tagObject->getId(),$this->tagIds )) {
+				$selectedTagObjects[] = $tagObject;
+			}
+		}
+		$news = $this->news ?? new NewsItem();
+		$news->setTitle($this->title);
+		$news->setLead(substr_replace($this->content, "...", 50));
+		$news->setContent($this->content);
+		$news->setImageFile($this->imageFile);
+		$news->setUrlPart(strtolower(IoUtils::stripSpecialChars($this->title)));
+		$news->setCategory($this->category);
+		$news->setTags($selectedTagObjects);
+		$newsDao->saveNews($news);
+	}
+
+	public function delete(NewsDao $newsDao) {
+		if (!$this->news) {
+			throw new PageNotFoundException('no id set');
+		}
+		$news = $this->news;
+		$newsDao->deleteNews($news);
+	}
+
+}

+ 60 - 0
src-php/app/internship/model/TagForm.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace internship\model;
+
+use n2n\web\dispatch\Dispatchable;
+use n2n\web\dispatch\map\bind\BindingDefinition;
+use n2n\impl\web\dispatch\map\val\ValNotEmpty;
+use n2n\web\dispatch\map\MappingResult;
+use internship\bo\NewsTag;
+use n2n\web\http\PageNotFoundException;
+
+class TagForm implements Dispatchable {
+	private ?NewsTag $tag = null;
+	protected string $name;
+
+	public function __construct(NewsTag $tag = null) {
+		$this->name = '';
+		if ($tag) {
+			$this->name = $tag->getName();
+			$this->id = $tag->getId();
+			$this->tag = $tag;
+		}
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getName() {
+		return $this->name;
+	}
+	/**
+	 * @param string $name
+	 */
+	public function setName(string $name) {
+		$this->name = $name;
+	}
+
+	private function _mapping(MappingResult $mr) {
+		$mr->setLabel('name', 'Name');
+	}
+
+	private function _validation(BindingDefinition $bd) {
+		$bd->val(array('name'), new ValNotEmpty());
+	}
+
+	public function save(NewsDao $newsDao) {
+		$tag = $this->tag ?? new NewsTag();
+		$tag->setName($this->name);
+		$newsDao->saveTag($tag);
+	}
+
+	public function delete(NewsDao $newsDao) {
+		if (!$this->tag) {
+			throw new PageNotFoundException('no id set');
+		}
+		$tag = $this->tag;
+		$newsDao->deleteTag($tag);
+	}
+
+}

+ 21 - 0
src-php/app/internship/model/TestModel.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace custom\model;
+
+use n2n\context\attribute\ThreadScoped;
+use n2n\context\attribute\SessionScoped;
+
+#[ThreadScoped]
+class TestModel {
+
+	#[SessionScoped]
+	private ?string $test = null;
+
+	function getTest() {
+		return $this->test;
+	}
+
+	function setTest(string $str) {
+		$this->test = $str;
+	}
+}

+ 38 - 0
src-php/app/internship/view/boilerplate.html.php

@@ -0,0 +1,38 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use n2n\web\ui\view\View;
+use n2n\core\N2N;
+use n2n\l10n\MessageContainer;
+use n2n\l10n\Message;
+
+// 1. Objekte der View: nicht zwingend notwendig
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+$request = HtmlView::request($view);
+
+// 2. Auslesen des Titels und der Beschreibung
+$title = $view->getParam('title', false, 'Example');
+$description = $view->getParam('lead', false);
+$mc = $view->getParam('mc');
+$view->assert($mc instanceof MessageContainer);
+
+
+// 3. Abfüllen der Tags im Header Bereich
+$html->meta()->setTitle($title);
+$html->meta()->addMeta(array('charset' => N2N::CHARSET));
+if (null !== $description) {
+	$html->meta()->addMeta(array('name' => 'description', 'content' => $description));
+}
+?>
+<!doctype html>
+<html lang="<?php $html->out($request->getN2nLocale()->getId())?>">
+<?php $html->headStart() ?>
+<?php $html->headEnd() ?>
+<?php $html->bodyStart() ?>
+<?php foreach ($mc->getAll() as $message) : $view->assert($message instanceof Message) ?>
+	<?php $html->out($message) ?>
+<?php endforeach; ?>
+<?php $view->importContentView() ?>
+
+<?php $html->bodyEnd() ?>
+</html>

+ 39 - 0
src-php/app/internship/view/categorydetail.html.php

@@ -0,0 +1,39 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use n2n\web\ui\view\View;
+use internship\bo\NewsItem;
+use internship\bo\NewsCategory;
+
+// freiwillige Zeile
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$newsCategory = $view->getParam('newsCategory');
+$view->assert($newsCategory instanceof NewsCategory);
+$mc = $view->getParam('mc');
+
+// Anweisung das Template zu nutzen
+$view->useTemplate('boilerplate.html', array('title' => 'Kategorie Detail ' . $newsCategory->getTitle(),
+		'lead' => $newsCategory->getLead(), 'mc' => $mc));
+?>
+<h1><?php $html->out($newsCategory->getTitle()) ?></h1>
+<p><?php $html->linkToController(array(), 'zurück zur Übersicht') ?></p>
+<hr>
+
+<?php foreach ($newsCategory->getNewsItems() as $newsItem): $view->assert($newsItem instanceof NewsItem) ?>
+<div>
+	<h2><?php $html->out($newsItem->getTitle()) ?></h2>
+	<p><?php $html->out($newsItem->getLead()) ?></p>
+	<p><?php $html->linkToController(array($newsCategory->getUrlPart(), 'detail',
+				$newsItem->getUrlPart(), $newsItem->getId()), 'weiterlesen') ?></p>
+	<p><?php $html->linkToController(array('createnews',
+				$newsItem->getCategory()->getUrlPart(), $newsItem->getId()), 'edit') ?></p>
+	<p><?php $html->linkToController(array('deletenews', $newsItem->getId()), 'delete') ?></p>
+
+</div>
+<hr>
+<?php endforeach ?>
+
+<p><?php $html->linkToController(array('createnews', $newsCategory->getUrlPart()), 'Neue News erfassen') ?></p>
+<p><?php $html->linkToController(['deletecategory', $newsCategory->getId()], 'Kategorie löschen') ?></p>
+

+ 43 - 0
src-php/app/internship/view/createcategory.html.php

@@ -0,0 +1,43 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use internship\model\CategoryForm;
+use internship\bo\NewsCategory;
+
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$formHtml = HtmlView::formHtml($view);
+
+$categoryForm = $view->getParam('categoryForm');
+$view->assert($categoryForm instanceof CategoryForm);
+$mc = $view->getParam('mc');
+$method = $view->getParam('method');
+
+$view->useTemplate('boilerplate.html', array('title' => 'Neue Kategorie', 'mc' => $mc));
+
+?>
+<h1>Kategorie Erfasssen / Editieren</h1>
+
+<?php $formHtml->open($categoryForm) ?>
+<?php $formHtml->messageList() ?>
+<div>
+	<?php $formHtml->label('title') ?><br />
+	<?php $formHtml->input('title', array('maxlength' => 120)) ?>
+</div>
+<div>
+	<?php $formHtml->label('content') ?><br />
+	<?php $formHtml->textarea('content', array('rows' => 5, 'cols' => 30)) ?>
+</div>
+<div>
+	<?php $formHtml->label('imageFile')?><br />
+	<?php $formHtml->inputFileWithLabel('imageFile') ?>
+</div>
+
+<?php $formHtml->buttonSubmit('save', 'Kategorie speichern')?>
+<?php if ($method === 'edit'):
+	$formHtml->buttonSubmit('delete', 'Kategorie löschen');
+endif;
+?>
+
+<?php $formHtml->close() ?>
+<p><?php $html->linkToController(array(), 'abbrechen und zurück zur Übersicht') ?></p>

+ 54 - 0
src-php/app/internship/view/createnews.html.php

@@ -0,0 +1,54 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use internship\model\NewsForm;
+use internship\bo\NewsCategory;
+use internship\bo\NewsTag;
+
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$formHtml = HtmlView::formHtml($view);
+
+$newsForm = $view->getParam('newsForm');
+$view->assert($newsForm instanceof NewsForm);
+$mc = $view->getParam('mc');
+$method = $view->getParam('method');
+
+
+$view->useTemplate('boilerplate.html', array('title' => 'Neue News', 'mc' => $mc));
+
+?>
+<h1>News Erfasssen / Editieren</h1>
+
+<?php $formHtml->open($newsForm) ?>
+<?php $formHtml->messageList() ?>
+<div>
+	<?php $formHtml->label('title') ?><br />
+	<?php $formHtml->input('title', array('maxlength' => 120)) ?>
+</div>
+<div>
+	<?php $formHtml->label('content') ?><br />
+	<?php $formHtml->textarea('content', array('rows' => 5, 'cols' => 30)) ?>
+</div>
+<div>
+	<?php $formHtml->label('imageFile')?><br />
+	<?php $formHtml->inputFileWithLabel('imageFile') ?>
+</div>
+<div>
+	<?php $formHtml->label('tagIds')?><br />
+	<ul>
+		<?php foreach ($newsForm->getTagOptions() as $NewsTagObject): $view->assert($NewsTagObject instanceof NewsTag) ?>
+			<li><?php $formHtml->inputCheckbox('tagIds[]',
+						$NewsTagObject->getId(), null, $NewsTagObject->getName()) ?></li>
+		<?php endforeach ?>
+	</ul>
+
+</div>
+
+<?php $formHtml->buttonSubmit('save', 'News speichern')?>
+<?php if ($method === 'edit'):
+	$formHtml->buttonSubmit('delete', 'News löschen');
+endif;
+?>
+<?php $formHtml->close() ?>
+<p><?php $html->linkToController(array(), 'abbrechen und zurück zur Übersicht') ?></p>

+ 36 - 0
src-php/app/internship/view/createtag.html.php

@@ -0,0 +1,36 @@
+<?php
+
+use n2n\impl\web\ui\view\html\HtmlView;
+use internship\model\TagForm;
+use internship\bo\NewsCategory;
+use internship\bo\NewsTag;
+
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$formHtml = HtmlView::formHtml($view);
+
+$tagForm = $view->getParam('tagForm');
+$view->assert($tagForm instanceof TagForm);
+$mc = $view->getParam('mc');
+$method = $view->getParam('method');
+
+
+$view->useTemplate('boilerplate.html', array('title' => 'Neuen Tag erfassen', 'mc' => $mc));
+
+?>
+<h1>Tag Erfasssen / Editierern</h1>
+
+<?php $formHtml->open($tagForm) ?>
+<?php $formHtml->messageList() ?>
+<div>
+	<?php $formHtml->label('name') ?><br />
+	<?php $formHtml->input('name', array('maxlength' => 50)) ?>
+</div>
+<?php $formHtml->buttonSubmit('save', 'Tag speichern')?>
+<?php if ($method === 'edit'):
+	$formHtml->buttonSubmit('delete', 'Tag löschen');
+endif;
+?>
+<?php $formHtml->close() ?>
+<p><?php $html->linkToController(array(), 'abbrechen und zurück zur Übersicht') ?></p>

+ 37 - 0
src-php/app/internship/view/deletecategory.html.php

@@ -0,0 +1,37 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use internship\model\CategoryForm;
+use internship\bo\NewsCategory;
+
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$formHtml = HtmlView::formHtml($view);
+
+$deleteCategoryForm = $view->getParam('DeleteCategoryForm');
+$view->assert($deleteCategoryForm instanceof CategoryForm);
+$mc = $view->getParam('mc');
+
+$view->useTemplate('boilerplate.html', array('title' => 'Kategorie löschen', 'mc' => $mc));
+
+?>
+<h1>Diese Kategorie löschen</h1>
+
+<?php $formHtml->open($deleteCategoryForm) ?>
+<?php $formHtml->messageList() ?>
+<div>
+	<?php $formHtml->label('title') ?><br />
+	<?php $formHtml->input('title', array('maxlength' => 120)) ?>
+</div>
+<div>
+	<?php $formHtml->label('content') ?><br />
+	<?php $formHtml->textarea('content', array('rows' => 5, 'cols' => 30)) ?>
+</div>
+<div>
+	<?php $formHtml->label('imageFile')?><br />
+	<?php $formHtml->inputFileWithLabel('imageFile') ?>
+</div>
+
+<?php $formHtml->buttonSubmit('delete', 'Kategorie löschen')?>
+<?php $formHtml->close() ?>
+<p><?php $html->linkToController(array(), 'abbrechen und zurück zur Übersicht') ?></p>

+ 48 - 0
src-php/app/internship/view/deletenews.html.php

@@ -0,0 +1,48 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use internship\model\NewsForm;
+use internship\bo\NewsCategory;
+use internship\bo\NewsTag;
+
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$formHtml = HtmlView::formHtml($view);
+
+$deleteNewsForm = $view->getParam('DeleteNewsForm');
+$view->assert($deleteNewsForm instanceof NewsForm);
+$mc = $view->getParam('mc');
+
+$view->useTemplate('boilerplate.html', array('title' => 'Delete News', 'mc' => $mc));
+
+?>
+<h1>Diese News löschen</h1>
+
+<?php $formHtml->open($deleteNewsForm) ?>
+<?php $formHtml->messageList() ?>
+<div>
+	<?php $formHtml->label('title') ?><br />
+	<?php $formHtml->input('title', array('maxlength' => 120)) ?>
+</div>
+<div>
+	<?php $formHtml->label('content') ?><br />
+	<?php $formHtml->textarea('content', array('rows' => 5, 'cols' => 30)) ?>
+</div>
+<div>
+	<?php $formHtml->label('imageFile')?><br />
+	<?php $formHtml->inputFileWithLabel('imageFile') ?>
+</div>
+<div>
+	<?php $formHtml->label('tagIds')?><br />
+	<ul>
+		<?php foreach ($deleteNewsForm->getTagOptions() as $NewsTagObject): $view->assert($NewsTagObject instanceof NewsTag) ?>
+			<li><?php $formHtml->inputCheckbox('tagIds[]',
+						$NewsTagObject->getId(), null, $NewsTagObject->getName()) ?></li>
+		<?php endforeach ?>
+	</ul>
+
+</div>
+
+<?php $formHtml->buttonSubmit('delete', 'News löschen')?>
+<?php $formHtml->close() ?>
+<p><?php $html->linkToController(array(), 'abbrechen und zurück zur Übersicht') ?></p>

+ 25 - 0
src-php/app/internship/view/deletetag.html.php

@@ -0,0 +1,25 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use internship\model\TagForm;
+
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$formHtml = HtmlView::formHtml($view);
+$deleteTagForm = $view->getParam('deleteTagForm');
+$view->assert($deleteTagForm instanceof TagForm);
+$mc = $view->getParam('mc');
+
+$view->useTemplate('boilerplate.html', array('title' => 'Tags editieren', 'mc' => $mc));
+?>
+<h1>Tag löschen</h1>
+
+<?php $formHtml->open($deleteTagForm) ?>
+<?php $formHtml->messageList() ?>
+<div>
+	<?php $formHtml->label('name') ?><br />
+	<?php $formHtml->input('name', array('maxlength' => 50)) ?>
+</div>
+<?php $formHtml->buttonSubmit('delete', 'Tag löschen')?>
+<?php $formHtml->close() ?>
+<p><?php $html->linkToController(array(), 'abbrechen und zurück zur Übersicht') ?></p>

+ 30 - 0
src-php/app/internship/view/newsdetail.html.php

@@ -0,0 +1,30 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use n2n\web\ui\view\View;
+use internship\bo\NewsItem;
+use internship\bo\NewsCategory;
+use internship\bo\NewsTag;
+
+// freiwillige Zeile
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$newsItem = $view->getParam('newsItem');
+$view->assert($newsItem instanceof NewsItem);
+$mc = $view->getParam('mc');
+
+
+$view->useTemplate('boilerplate.html', array('title' => $newsItem->getTitle(),
+		'lead' => $newsItem->getlead(), 'mc' => $mc));
+?>
+<h1><?php $html->out($newsItem->getTitle()) ?></h1>
+<?php foreach ($newsItem->getTags() as $newsTag): $view->assert($newsTag instanceof NewsTag) ?>
+	<b><?php $html->out($newsTag->getName()) ?></b>
+<?php endforeach; ?>
+<p><?php $html->out($newsItem->getContent()) ?></p>
+<p><?php $html->linkToController(array('createnews',
+			$newsItem->getCategory()->getUrlPart(), $newsItem->getId()), 'edit') ?></p>
+<p><?php $html->linkToController(array('deletenews', $newsItem->getId()), 'delete') ?></p>
+
+
+<p><?php $html->linkToController(array($newsItem->getCategory()->getUrlPart()), 'zurück zur Kategorie') ?></p>

+ 35 - 0
src-php/app/internship/view/overview.html.php

@@ -0,0 +1,35 @@
+<?php
+use n2n\impl\web\ui\view\html\HtmlView;
+use n2n\web\ui\view\View;
+use internship\bo\NewsCategory;
+
+// freiwillige Zeile
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+/**
+ * @var \internship\bo\NewsCategory[] $newsCategories
+ */
+$newsCategories = $view->getParam('newsCategories');
+$mc = $view->getParam('mc');
+
+// Anweisung das Template zu nutzen
+$view->useTemplate('boilerplate.html', array('title' => 'Übersicht', 'mc' => $mc));
+?>
+<h1>Unsere News Kategorien</h1>
+<p>Hier folgen die News</p>
+<hr>
+<?php foreach ($newsCategories as $newsCategory): $view->assert($newsCategory instanceof NewsCategory) ?>
+<div>
+	<h2><?php $html->linkToController($newsCategory->getUrlPart(), $newsCategory->getTitle()) ?></h2>
+
+	<p><?php $html->escP($newsCategory->getContent()) ?></p>
+	<p><?php $html->linkToController(['createcategory', $newsCategory->getId()], 'Kategorie editieren') ?></p>
+	<p><?php $html->linkToController(['deletecategory', $newsCategory->getId()], 'Kategorie löschen') ?></p>
+</div>
+<hr>
+<?php endforeach ?>
+
+<p><?php $html->linkToController('createcategory', 'Neue Kategorie erfassen') ?></p>
+<p><?php $html->linkToController('createtag', 'Neuen Tag erfassen') ?></p>
+<p><?php $html->linkToController('taglist', 'Tag auflistung') ?></p>

+ 40 - 0
src-php/app/internship/view/taglist.html.php

@@ -0,0 +1,40 @@
+<?php
+
+use n2n\impl\web\ui\view\html\HtmlView;
+use internship\model\TagForm;
+use internship\bo\NewsCategory;
+use internship\bo\NewsTag;
+use internship\bo\NewsItem;
+
+$view = HtmlView::view($this);
+$html = HtmlView::html($view);
+
+$formHtml = HtmlView::formHtml($view);
+
+$newsTags = $view->getParam('newsTags');
+$mc = $view->getParam('mc');
+
+$view->useTemplate('boilerplate.html', array('title' => 'Tags editieren', 'mc' => $mc));
+
+?>
+	<p><?php $html->linkToController(array(), 'zurück zur Übersicht') ?></p>
+	<hr>
+<?php foreach ($newsTags as $newsTag): $view->assert($newsTag instanceof NewsTag) ?>
+	<div>
+		<h2><?php $html->out($newsTag->getName()) ?></h2>
+		<p>Enthält folgende NewsItems:</p>
+		<ul>
+			<?php foreach ($newsTag->getNewsItems() as $newsItem):  $view->assert($newsItem instanceof NewsItem) ?>
+				<li><?php  $html->out($newsItem->getTitle()) ?></li>
+				<p><?php $html->linkToController(array($newsItem->getCategory()->getUrlPart(),
+							'detail', $newsItem->getUrlPart(), $newsItem->getId()), 'weiterlesen') ?></p>
+				<p><?php $html->linkToController(array('createnews',
+							$newsItem->getCategory()->getUrlPart(), $newsItem->getId()), 'edit') ?></p>
+
+			<?php endforeach ?>
+		</ul>
+		<p><?php $html->linkToController(['createtag', $newsTag->getId()],'Tag editieren') ?></p>
+		<p><?php $html->linkToController(['deletetag', $newsTag->getId()], 'Tag löschen') ?></p>
+		<hr>
+	</div>
+<?php endforeach ?>

+ 109 - 0
src-php/atusch.php

@@ -0,0 +1,109 @@
+<?php
+declare(strict_types=1);
+
+abstract class Abst {
+	protected ?int $prop = null;
+	abstract function toBeReplaced($tell) : string;
+
+	function tell() {
+		echo 'hoi';
+	}
+	/** property möglich */
+	public string $name;
+	function setName($name) {
+		$this->name = $name;
+	}
+	function getName() {
+		return $this->name;
+	}
+
+	public function tellme() : string {
+		return 'I tell ' . $this->prop;
+	}
+}
+class Normal {
+}
+
+class AbstChild extends Abst {
+
+
+	public function toBeReplaced($tell) : string {
+		return 'i fill Abst with: ' . $tell;
+	}
+
+
+	function tell() {
+		echo 'tschau!';
+	}
+
+	static function create(): AbstChild {
+		$normal = new AbstChild();
+		$normal->prop = 1;
+		return $normal;
+	}
+}
+
+
+$normal = AbstChild::create();
+echo $normal->tellme();
+
+//die();
+
+
+/*
+class NoWorkAbstChild extends Abst {
+	public function ifill() : string {
+		return "i fill";
+	}
+}
+*/
+interface AInterface {
+	/** Interface kann kein property haben */
+	public function toBeReplaced() : string;
+}
+interface AOtherInterface {
+	/** Interface kann kein property haben */
+	public function toBeReplaced() : string;
+}
+
+class WithInterface implements AInterface, AOtherInterface {
+	public function toBeReplaced(): string {
+		return 'i fill interface';
+	}
+}
+
+trait TraitOne {
+	public function tellme() {
+		echo 'do tell me';
+		echo '<br>';
+	}
+}
+trait TraitTwo {
+	public function listenme() {
+		echo 'i listen you';
+		echo '<br>';
+	}
+}
+
+class MyMe {
+	use TraitOne, TraitTwo;
+	/** wenn mehr als 1 Verrerbung nötig */
+}
+
+
+$abstchild = new AbstChild();
+$abstchild->setName('AbstName');
+echo $abstchild->toBeReplaced('gugus');
+echo "<br>";
+echo $abstchild->getName();
+echo "<br>";
+
+$withinterface = new WithInterface();
+echo $withinterface->toBeReplaced();
+echo "<br>";
+
+$myme = new MyMe();
+$myme->tellme();
+$myme->listenme();
+
+//		$this->tagIds = array_map(fn (NewsTag $t) => $t->getId(), $news->getTags()->getArrayCopy());

+ 108 - 3
src-php/test/internship/controller/ArticleControllerTest.php

@@ -6,6 +6,11 @@ use PHPUnit\Framework\TestCase;
 use n2n\test\TestEnv;
 use internship\test\ArticleTestEnv;
 use util\GeneralTestEnv;
+use n2n\web\http\PageNotFoundException;
+use n2n\web\http\BadRequestException;
+use internship\bo\Article;
+use PHPUnit\Util\Json;
+use function MongoDB\BSON\toJSON;
 
 
 class ArticleControllerTest extends TestCase {
@@ -18,9 +23,9 @@ class ArticleControllerTest extends TestCase {
 		GeneralTestEnv::tearDown();
 
 		$tx = TestEnv::createTransaction();
-		$article1 = ArticleTestEnv::setUpArticle('Title 1', 'Loren ipsum 1', 'teaser');
-		$article2 = ArticleTestEnv::setUpArticle('Title 2', 'Loren ipsum 2', 'news');
-		$article3 = ArticleTestEnv::setUpArticle('Title 3', 'Loren ipsum 3', 'news');
+		$article1 = ArticleTestEnv::setUpArticle('Title 1', 'Loren ipsum 1', 'sport');
+		$article2 = ArticleTestEnv::setUpArticle('Title 2', 'Loren ipsum 2', 'national');
+		$article3 = ArticleTestEnv::setUpArticle('Title 3', 'Loren ipsum 3', 'national');
 		$tx->commit();
 
 		$this->article1Id = $article1->getId();
@@ -40,5 +45,105 @@ class ArticleControllerTest extends TestCase {
 		$this->assertEquals('Title 2', $articleStructs[1]['title']);
 		$this->assertEquals('Title 1', $articleStructs[2]['title']);
 	}
+	function testGetDoArticlesByCategoryName() {
+		$response = TestEnv::http()->newRequest()
+				->get(['api', 'articles', 'sport'])
+				->exec();
+
+		$articleStructs = $response->parseJson();
+		$this->assertCount(1, $articleStructs);
+		$this->assertEquals('Title 1', $articleStructs[0]['title']);
+
+		$response = TestEnv::http()->newRequest()
+				->get(['api', 'articles', 'national'])
+				->exec();
+
+		$articleStructs = $response->parseJson();
+		$this->assertCount(2, $articleStructs);
+		$this->assertEquals('Title 3', $articleStructs[0]['title']);
+		$this->assertEquals('Title 2', $articleStructs[1]['title']);
+	}
+	function testGetDoArticle() {
+		$response = TestEnv::http()->newRequest()
+				->get(['api', 'article', '1'])
+				->exec();
+
+		$articleStructs = $response->parseJson();
+		$this->assertCount(4, $articleStructs);
+		$this->assertEquals('Title 1', $articleStructs['title']);
+		$this->assertEquals('Loren ipsum 1', $articleStructs['text']);
+		$this->assertEquals('sport', $articleStructs['categoryName']);
+		$this->assertEquals('1', $articleStructs['id']);
+	}
+	function testGetDoArticlePageNotFound() {
+		$this->expectException(PageNotFoundException::class);
+		TestEnv::http()->newRequest()
+				->get(['api', 'article', '4'])
+				->exec();
+	}
+
+	function testPostDoNewArticleBadCategory() {
+		$this->expectException(BadRequestException::class);
+		TestEnv::http()->newRequest()
+				->post(['api', 'newarticle'])
+				->bodyJson(['title' => 'Title 5', 'categoryName' => 'news', 'text' => 'Loren ipsum 5'])
+				->exec();
+
+	}
+	function testPostDoNewArticle() {
+		$response = TestEnv::http()->newRequest()
+				->post(['api', 'newarticle'])
+				->bodyJson(['title' => 'Title 4', 'categoryName' => 'international', 'text' => 'Loren ipsum 4'])
+				->exec();
+		$articleStruct = $response->parseJson();
+		$article = TestEnv::em()->find(Article::getClass(), $articleStruct['id']);
+		$this->assertEquals('Title 4', $article->getTitle());
+		$this->assertEquals('Loren ipsum 4', $article->getText());
+		$this->assertEquals('international', $article->getCategoryName());
+		$this->assertEquals($articleStruct['id'], $article->getId());
+
+		$response = TestEnv::http()->newRequest()
+				->get(['api', 'articles'])
+				->exec();
+
+		$articleStructs = $response->parseJson();
+		$this->assertCount(4, $articleStructs);
+	}
+	function testPutDoArticle() {
+		TestEnv::http()->newRequest()
+				->put(['api', 'article', '1'])
+				->bodyJson(['title' => 'Title 4', 'categoryName' => 'international', 'text' => 'Loren ipsum 4'])
+				->exec();
+
+		$response = TestEnv::http()->newRequest()
+				->get(['api', 'article', '1'])
+				->exec();
+
+		$articleStructs = $response->parseJson();
+		$this->assertCount(4, $articleStructs);
+		$this->assertEquals('Title 4', $articleStructs['title']);
+		$this->assertEquals('Loren ipsum 4', $articleStructs['text']);
+		$this->assertEquals('international', $articleStructs['categoryName']);
+		$this->assertEquals('1', $articleStructs['id']);
+	}
+	function testDeleteDoArticle() {
+		TestEnv::http()->newRequest()
+				->delete(['api', 'article', '1'])
+				->exec();
+
+		$response = TestEnv::http()->newRequest()
+				->get(['api', 'articles'])
+				->exec();
+
+		$articleStructs = $response->parseJson();
+		$this->assertCount(2, $articleStructs);
+
+		$this->expectException(PageNotFoundException::class);
+		TestEnv::http()->newRequest()
+				->get(['api', 'article', '1'])
+				->exec();
+	}
+
+
 
 }

+ 2 - 1
src-php/var/etc/.gitignore

@@ -2,4 +2,5 @@
 /n2n-impl-web-dispatch
 /n2n
 /n2n-web
-/n2n-impl-persistence-orm
+/n2n-impl-persistence-orm
+/n2n-persistence

+ 7 - 1
src-php/var/etc/internship/app.ini

@@ -1,5 +1,11 @@
 [routing]
 controllers[/api] = "internship\controller\ArticleController"
+controllers[/david] = "internship\controller\ArticleControllerDavid"
+controllers[/news] = "internship\controller\NewsController"
+
 
 [orm]
-entities[] = "internship\bo\Article"
+entities[] = "internship\bo\Article"
+entities[] = "internship\bo\NewsCategory"
+entities[] = "internship\bo\NewsItem"
+entities[] = "internship\bo\NewsTag"

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است