diff --git a/.config/config_global.php b/.config/config_global.php index b70856f..613f72b 100644 --- a/.config/config_global.php +++ b/.config/config_global.php @@ -98,5 +98,7 @@ define('DB_PASS', '2YcEOiFRUhk5ELeT9gZ3'); define('TBL_FRONT_NAVIGATION', 'ahd_navigation'); define('TBL_IMAGE_TEXT', 'ahd_image_text'); define('TBL_IMAGE', 'ahd_image'); +define('TBL_TEXT', 'ahd_text'); +define('TBL_SUBLINE', 'ahd_subline'); require_once(PATH_CNF . 'config_version.php'); \ No newline at end of file diff --git a/backend/editor/.config/editor_config.php b/backend/editor/.config/editor_config.php index 7a8d1d3..3fde2e1 100644 --- a/backend/editor/.config/editor_config.php +++ b/backend/editor/.config/editor_config.php @@ -10,7 +10,7 @@ $editor['name'] = 'AHD Allradhaus GmbH'; if (ENVIRONMENT === 'local') { - $editor['editorUrl'] = SCHEME . '://csteinle.ddns.net/rist-editors/'; + $editor['editorUrl'] = SCHEME . '://csteinle.ddnss.de/rist-editors/'; $editor['editorVersion'] = '2.0.0'; } elseif (ENVIRONMENT === 'production') @@ -22,10 +22,18 @@ $editor['webserviceUrl'] = HOST_URL . str_replace(PATH_ROOT, '', dirname(__DIR__ $editor['imageTypes'] = array('image/png', 'image/jpeg', 'image/gif'); $editor['imageDimension']['image'] = array('width' => 480, 'height' => null, 'standardImage' => HTML_IMG . 'standard_upload.jpg', 'quality' => 95); -$editor['imageDimension']['keyVisual'] = array('width' => 1368, 'height' => 342, 'standardImage' => HTML_IMG . 'standard_upload.jpg', 'quality' => 95, ); +$editor['imageDimension']['keyVisual'] = array('width' => 1368, 'height' => 342, 'standardImage' => HTML_IMG . 'header_upload.jpg', 'quality' => 95); $editor['imageDimension']['thumb'] = array('width' => 152, 'quality' => 90); -$editor['imageDimension']['orig'] = array('width' => 2560, 'height' => 2560, 'quality' => 97); +$editor['imageDimension']['orig'] = array('width' => 1920, 'height' => 1920, 'quality' => 93); $editor['contentElements'] = array('subline', 'text', 'textimage'); $editor['backendPrefix'] = PATH_PREFIX . '/' . str_replace(PATH_ROOT, '', dirname(__DIR__)); -$editor['backendUrl'] = HOST_URL . str_replace(PATH_ROOT, '', dirname(__DIR__)) . '/webservice/requestData.php'; \ No newline at end of file +$editor['backendUrl'] = HOST_URL . str_replace(PATH_ROOT, '', dirname(__DIR__)) . '/webservice/requestData.php'; + +$editor['linkReplacements'] = array('AHD'); +$editor['editorPrefix'] = 'ahd'; + +$editor['mediaPath'] = PATH_ROOT . 'media/' . $editor['editorPrefix'] . '/'; +$editor['mediaPrefix'] = HTML_MED . $editor['editorPrefix'] . '/'; + +$editor['fileExtensions'] = array('image/png' => 'png', 'image/jpeg' => 'jpg', 'image/gif' => 'gif'); diff --git a/backend/editor/index.php b/backend/editor/index.php index 001f5c4..b36f601 100644 --- a/backend/editor/index.php +++ b/backend/editor/index.php @@ -22,5 +22,6 @@ $loader = require PATH_ROOT . '/vendor/autoload.php'; $registry = \Helper\Registry::getInstance(); $registry->editorConfig = $editor; $registry->editorConfig['token'] = session_id(); +$registry->editorConfig['sessionLanguage'] = $_SESSION['lang']; $route = new \Route\FrontendRoute($siteID, true); \ No newline at end of file diff --git a/backend/editor/webservice/requestData.php b/backend/editor/webservice/requestData.php index 5326af1..cc49a78 100644 --- a/backend/editor/webservice/requestData.php +++ b/backend/editor/webservice/requestData.php @@ -6,22 +6,231 @@ * * @copyright CS medien- & kommunikationssysteme (http://www.steinle-computer.de) */ - +//file_put_contents(__DIR__ . '/request.log', var_export($_POST, true) . "\n\n", FILE_APPEND); +/** + * Fehlende Authentifizierung abfangen + */ if (!isset($_POST['token']) || !is_string($_POST['token']) || empty($_POST['token'])) { die(); } +$token = $_POST['token']; +unset($_POST['token']); -session_start($_POST['token']); +/** + * Fehlende SESSION abfangen + */ +session_start($token); if (!isset($_SESSION['userID']) || !is_numeric($_SESSION['userID']) || $_SESSION['userID'] < 1) { die(); } - if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) { die(); } -file_put_contents(__DIR__ . '/request.log', var_export($_POST, true) . "\n\n", FILE_APPEND); -echo json_encode(1); \ No newline at end of file +/** + * Fehlende Variablen abfangen + */ +if (!isset($_POST['request']) || $_POST['request'] === '') +{ + die(); +} +$request = $_POST['request']; +unset($_POST['request']); + +$pathRoot = dirname(dirname(dirname(__DIR__))) . '/'; + +require_once($pathRoot . '.config/config_global.php'); +require_once(dirname(__DIR__) . '/.config/editor_config.php'); +$loader = require PATH_ROOT . '/vendor/autoload.php'; + +$registry = Helper\Registry::getInstance(); +$registry->editorConfig = $editor; +if (isset($_POST['navID'])) +{ + $registry->navID = $_POST['navID']; +} + +$modelData = array(); +$dbData = $_POST; +switch ($request) +{ + case 'getText': + $modelData = Model\TextModel::getItem($dbData['ID']); + break; + + case 'updateText': + $dbData['text'] = Helper\Text::prepareText($dbData['text']); + $modelData = Model\TextModel::update($dbData); + break; + + case 'getSubline': + $modelData = Model\SublineModel::getItem($dbData['ID']); + break; + + case 'updateSubline': + $dbData['text'] = trim(str_replace(array('

', '

'), '', $dbData['text'])); + $modelData = Model\SublineModel::update($dbData); + break; + + case 'getImageText': + $modelData = Model\ImageTextModel::getItem($dbData['ID']); + break; + + case 'updateImageText': + $dbData['text'] = Helper\Text::prepareText($dbData['text']); + $modelData = Model\ImageTextModel::update($dbData); + break; + + case 'getNavigation': + Model\NavigationModel::setFilter('1=1'); + $modelData = Model\NavigationModel::getStructuredIndex(); + break; + + case 'updateNavigation': + $modelData = Model\NavigationModel::update($dbData); + break; + + case 'insertNavigation': + $dbData['navLink'] = Model\NavigationModel::getUniqueNavLink($dbData['navStart'], $dbData['navName']); + $dbData['navHeadline'] = $dbData['navName']; + $dbData['navID'] = Model\NavigationModel::insert($dbData); + + $mediaPath = $registry->editorConfig['mediaPath'] . $dbData['navID'] . '/'; + $imageName = end(explode('/', $registry->editorConfig['imageDimension']['keyVisual']['standardImage'])); + Model\ImageModel::copyStandardImage($mediaPath, $imageName, 'keyVisual'); + + $modelData = Model\NavigationModel::updateSortAfterInsert($dbData); + break; + + case 'updateNavigationSort': + $sortData = explode(',', $dbData['sortOrder']); + $modelData = Model\NavigationModel::updateSort($sortData); + break; + + case 'getHeadline': + $modelData = Model\NavigationModel::getItem($dbData['ID']); + break; + + case 'updateHeadline': + $dbData['navHeadline'] = trim(str_replace(array('

', '

'), '', $dbData['navHeadline'])); + $modelData = Model\NavigationModel::update($dbData); + break; + + case 'insertContent': + $sortData = explode(',', $dbData['sortOrder']); + + $navData = array(); + foreach ($sortData as $key => $contentElement) + { + $contentData = explode('_', $contentElement); + $contentData[1] = intval($contentData[1]); + if ($contentData[1] === 0) + { + switch ($contentData[0]) + { + case 'Text': + $newID = $modelData = Model\TextModel::insert(array('text' => 'Text')); + $contentData[1] = $newID; + break; + + case 'Subline': + $newID = $modelData = Model\SublineModel::insert(array('text' => 'Überschrift')); + $contentData[1] = $newID; + break; + + case 'ImageText': + $newID = $modelData = Model\ImageTextModel::insert(array('text' => 'Text', 'type' => 'small', 'navID' => $dbData['navID'])); + $contentData[1] = $newID; + break; + } + } + $navData[] = array('Controller' => $contentData[0], 'ID' => $contentData[1]); + } + $navContent = json_encode($navData); + + Model\NavigationModel::update(array('navID' => $dbData['navID'], 'navContent' => $navContent)); + break; + + case 'updateContentSort': + $sortData = explode(',', $dbData['sortOrder']); + + $navData = array(); + foreach ($sortData as $key => $contentElement) + { + $contentData = explode('_', $contentElement); + $navData[] = array('Controller' => $contentData[0], 'ID' => $contentData[1]); + } + $navContent = json_encode($navData); + + $modelData = Model\NavigationModel::update(array('navID' => $dbData['navID'], 'navContent' => $navContent)); + break; + + case 'deleteContentElement': + Model\NavigationModel::init(array($dbData['navID']), true); + + $navContent = Model\NavigationModel::getContents(); + foreach ($navContent as $key => $contentElement) + { + if ($contentElement['Controller'] === $dbData['editorType'] && $contentElement['ID'] === intval($dbData['editorDataID'])) + { + unset($navContent[$key]); + } + } + + $newNavData = array_values($navContent); + $navContent = json_encode($newNavData); + $modelData = Model\NavigationModel::update(array('navID' => $dbData['navID'], 'navContent' => $navContent)); + + switch ($dbData['editorType']) + { + case 'Subline': + Model\SublineModel::delete($dbData['editorDataID']); + break; + case 'Text': + Model\TextModel::delete($dbData['editorDataID']); + break; + case 'ImageText': + Model\ImageTextModel::delete($dbData['editorDataID']); + break; + } + break; + + case 'getKeyVisual': + Model\NavigationModel::init(array($dbData['ID']), true); + $keyVisualContent = Model\NavigationModel::getKeyVisual(); + Model\ImageModel::setFilter('imageID IN (' . implode(', ', $keyVisualContent['IDs']) . ')'); + $images = Model\ImageModel::getIndex(); + $modelData = $keyVisualContent; + $modelData['images'] = $images; + break; + + case 'updateKeyVisual': + Model\NavigationModel::init(array($dbData['navID']), true); + $keyVisualContent = Model\NavigationModel::getKeyVisual(); + $keyVisualContent['Type'] = $dbData['Type']; + $keyVisualJSON = json_encode($keyVisualContent, JSON_UNESCAPED_UNICODE); + $modelData = Model\NavigationModel::update(array('navID' => $dbData['navID'], 'navKeyVisual' => $keyVisualJSON)); + break; + + case 'updateImage': + $modelData = Model\ImageModel::update($dbData); + break; + + case 'appendKeyVisual': + $imageID = Model\ImageModel::insert($dbData); + Model\NavigationModel::init(array($dbData['navID']), true); + $keyVisualContent = Model\NavigationModel::getKeyVisual(); + $keyVisualContent['IDs'][] = $imageID; + $keyVisualJSON = json_encode($keyVisualContent, JSON_UNESCAPED_UNICODE); + $modelData = Model\NavigationModel::update(array('navID' => $dbData['navID'], 'navKeyVisual' => $keyVisualJSON)); + break; +} + +if (!empty($modelData)) +{ + echo json_encode($modelData); +} + diff --git a/css/ahd.css b/css/ahd.css index b5c3680..4585446 100644 --- a/css/ahd.css +++ b/css/ahd.css @@ -63,6 +63,24 @@ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { margin-left: 30px; } +.keyvisual { + height: 285px; + overflow: hidden; + position: relative; +} + +.carousel-inner { + height: 100%; +} + +.keyvisual img { + position: absolute; +} + +.slider img:not(:first-of-type) { + display: none; +} + @media screen and (min-width: 768px) { .subaru_logo { margin-top: 12px; diff --git a/css/ahd.min.css b/css/ahd.min.css index 21cfe3a..6f6634d 100644 --- a/css/ahd.min.css +++ b/css/ahd.min.css @@ -1 +1 @@ -body{padding:50px 0;font-family:Verdana,Arial,sans-serif;text-align:justify}li.dropdown-submenu{position:relative}li.dropdown-submenu:hover>ul.dropdown-menu{display:block}li.dropdown:hover>ul.dropdown-menu{display:block}ul.dropdown-menu.pull-left{margin-top:-6px;border-top-left-radius:0;border-bottom-left-radius:0;border-left:0;top:0;left:100%}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-weight:bold}#footer h3{text-decoration:underline}#footer:hover .navbar-brand{height:auto}#footer .navbar-brand{margin-left:0}#footer .navbar-brand p{font-size:14px}.container-fluid{max-width:1600px}.container-fluid .img-responsive{margin:0 auto}.caret.rotate-left{transform:rotate(270deg)}.navbar-fixed-top .pull-right img{margin-top:15px;margin-left:30px}@media screen and (min-width:768px){.subaru_logo{margin-top:12px;width:149px;height:25px;background-image:url('../images/subaru/subaru_logo.png')}.daihatsu_logo{width:150px;height:25px;margin-left:15px;margin-top:12px;background-image:url('../images/daihatsu_logo.png')}}@media(max-width:767px){#main-navbar{position:absolute;left:80px} .navbar-collapse.in{overflow-y:visible} #main-navbar a.dropdown-toggle{display:block} #main-navbar ul.dropdown-menu{float:left} .container>.navbar-header{margin:0} .subaru_logo{margin-top:12px;width:48px;height:25px;background-image:url('../images/subaru/subaru_logo.png')} .daihatsu_logo{width:42px;height:25px;margin-left:15px;margin-top:12px;background-image:url('../images/daihatsu_logo.png')}}@media(max-width:642px){#footer .ahd{display:none}} \ No newline at end of file +body{padding:50px 0;font-family:Verdana,Arial,sans-serif;text-align:justify}li.dropdown-submenu{position:relative}li.dropdown-submenu:hover>ul.dropdown-menu{display:block}li.dropdown:hover>ul.dropdown-menu{display:block}ul.dropdown-menu.pull-left{margin-top:-6px;border-top-left-radius:0;border-bottom-left-radius:0;border-left:0;top:0;left:100%}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-weight:bold}#footer h3{text-decoration:underline}#footer:hover .navbar-brand{height:auto}#footer .navbar-brand{margin-left:0}#footer .navbar-brand p{font-size:14px}.container-fluid{max-width:1600px}.container-fluid .img-responsive{margin:0 auto}.caret.rotate-left{transform:rotate(270deg)}.navbar-fixed-top .pull-right img{margin-top:15px;margin-left:30px}.keyvisual{height:285px;overflow:hidden;position:relative}.carousel-inner{height:100%}.keyvisual img{position:absolute}.slider img:not(:first-of-type){display:none}@media screen and (min-width:768px){.subaru_logo{margin-top:12px;width:149px;height:25px;background-image:url('../images/subaru_logo.png')}.daihatsu_logo{width:150px;height:25px;margin-left:15px;margin-top:12px;background-image:url('../images/daihatsu_logo.png')}}@media(max-width:767px){#main-navbar{position:absolute;left:80px}.navbar-collapse.in{overflow-y:visible}#main-navbar a.dropdown-toggle{display:block}#main-navbar ul.dropdown-menu{float:left}.container>.navbar-header{margin:0}.subaru_logo{margin-top:12px;width:48px;height:25px;background-image:url('../images/subaru_logo.png')}.daihatsu_logo{width:42px;height:25px;margin-left:15px;margin-top:12px;background-image:url('../images/daihatsu_logo.png')}}@media(max-width:642px){#footer .ahd{display:none}} \ No newline at end of file diff --git a/images/header_upload.jpg b/images/header_upload.jpg new file mode 100644 index 0000000..d385501 Binary files /dev/null and b/images/header_upload.jpg differ diff --git a/index.php b/index.php index 17eaa41..e5549f3 100644 --- a/index.php +++ b/index.php @@ -7,14 +7,14 @@ * @copyright CS medien- & kommunikationssysteme (http://www.steinle-computer.de) */ -$url = (!isset($_GET['url']) || empty($_GET['url'])) ? 'allradhaus' : '/' . $_GET['url']; +$url = (!isset($_GET['url']) || empty($_GET['url'])) ? 'ahd_allradhaus' : '/' . $_GET['url']; $pathRoot = __DIR__ . '/'; require_once($pathRoot . '.config/config_global.php'); +require_once($pathRoot . 'backend/editor/.config/editor_config.php'); $loader = require __DIR__ . '/vendor/autoload.php'; -$route = new \Route\FrontendRoute($url); +$registry = \Helper\Registry::getInstance(); +$registry->editorConfig = $editor; -//$array = array(0 => array('Model' => 'ImageText', 'ID' => 1)); -//echo '
';
-//var_dump(json_encode($array));
\ No newline at end of file
+$route = new \Route\FrontendRoute($url);
\ No newline at end of file
diff --git a/js/ahd.js b/js/ahd.js
new file mode 100644
index 0000000..2a06fed
--- /dev/null
+++ b/js/ahd.js
@@ -0,0 +1,367 @@
+/**
+ * Created by CS medien- & kommunikationssysteme.
+ * @author Christian Steinle
+ * @date 24.12.2016
+ *
+ * @copyright CS medien- & kommunikationssysteme (http://www.steinle-computer.de)
+ */
+
+$(document).ready(function () {
+    slider.init();
+    kenBurns.init();
+});
+
+$(window).resize(function () {
+    slider.initVariables();
+    kenBurns.initVariables();
+});
+
+var slider = {
+    interval: null,
+    displayTime: 4000,
+    animationTime: 400,
+
+    actualImageID: -1,
+    actualImage: null,
+    nextImageID: 0,
+    nextImage: null,
+    countImages: 0,
+
+    animationWidth: 0,
+    animationHeight: 0,
+
+    sliderElement: null,
+    sliderImages: null,
+
+    availableAnimations: [
+        'left',
+        'top',
+        'width',
+        'height',
+        'opacity'
+    ],
+    animation: {},
+
+
+    init: function () {
+        slider.initElements();
+        slider.initVariables();
+        slider.interval = window.setInterval(slider.doAnimation, slider.displayTime);
+    },
+
+
+    initElements: function () {
+        slider.sliderElement = $('.slider');
+        slider.sliderImages = $('img', slider.sliderElement);
+    },
+
+
+    initVariables: function () {
+        slider.countImages = slider.sliderImages.length;
+        if (slider.countImages === 0) {
+            return false;
+        }
+        slider.animationWidth = slider.sliderElement.width();
+        slider.animationHeight = keyVisualData.height * slider.animationWidth / keyVisualData.width;
+        slider.sliderElement.css('height', slider.animationHeight);
+        slider.setNextImages();
+    },
+
+
+    setNextImages: function () {
+        ++slider.actualImageID;
+        ++slider.nextImageID;
+
+        if (slider.actualImageID === slider.countImages) {
+            slider.actualImageID = 0;
+        }
+
+        if (slider.nextImageID === slider.countImages) {
+            slider.nextImageID = 0;
+        }
+
+        slider.actualImage = $(slider.sliderImages[slider.actualImageID]);
+        slider.nextImage = $(slider.sliderImages[slider.nextImageID]);
+    },
+
+
+    doAnimation: function () {
+        if (slider.countImages < 2) {
+            return false;
+        }
+
+        var animationKey = Math.floor(Math.random() * slider.availableAnimations.length);
+        var animationName = slider.availableAnimations[animationKey];
+
+        slider.resetUnusedImages();
+
+        switch (animationName) {
+            case 'left':
+                slider.getShiftLeft();
+                break;
+            case 'top':
+                slider.getShiftTop();
+                break;
+            case 'width':
+                slider.getWidth();
+                break;
+            case 'height':
+                slider.getHeight();
+                break;
+            case 'opacity':
+                slider.getOpacity();
+                break;
+            default:
+                slider.getShiftLeft();
+                break;
+        }
+
+        slider.actualImage.show().animate(slider.animation.actual, slider.animationTime);
+        slider.nextImage.show().animate(slider.animation.next, slider.animationTime);
+
+        slider.setNextImages();
+    },
+
+    resetUnusedImages: function () {
+        slider.sliderImages.each(function (imageID, imageElement) {
+            var image = $(imageElement);
+
+            if (imageID !== slider.actualImageID && imageID !== slider.nextImageID) {
+                image.hide();
+            }
+
+            image.css({
+                left: 0,
+                top: 0,
+                width: slider.animationWidth,
+                height: slider.animationHeight,
+                opacity: 1
+            });
+
+        });
+    },
+
+
+    getShiftLeft: function () {
+        slider.nextImage.css({left: slider.animationWidth});
+        slider.animation.next = {left: 0};
+        slider.animation.actual = {left: -1 * slider.animationWidth, opacity: 0};
+    },
+
+
+    getShiftTop: function () {
+        slider.nextImage.css({top: slider.animationHeight});
+        slider.animation.next = {top: 0};
+        slider.animation.actual = {top: -1 * slider.animationHeight, opacity: 0};
+    },
+
+
+    getWidth: function () {
+        slider.nextImage.css({width: 0, left: slider.animationWidth / 2, opacity: 0});
+        slider.animation.next = {width: slider.animationWidth, left: 0, opacity: 1};
+        slider.animation.actual = {width: 0, left: slider.animationWidth / 2, opacity: 0};
+    },
+
+
+    getHeight: function () {
+        slider.nextImage.css({height: 0, top: slider.animationHeight / 2, opacity: 0});
+        slider.animation.next = {height: slider.animationHeight, top: 0, opacity: 1};
+        slider.animation.actual = {height: 0, top: slider.animationHeight / 2, opacity: 0};
+    },
+
+
+    getOpacity: function () {
+        slider.nextImage.css({opacity: 0});
+        slider.animation.next = {opacity: 1};
+        slider.animation.actual = {opacity: 0};
+    }
+};
+
+
+var kenBurns = {
+    interval: null,
+    displayTime: 5000,
+    animationTime: 400,
+    scaleFactor: 1.2,
+
+    actualImageID: -1,
+    actualImage: null,
+    nextImageID: 0,
+    nextImage: null,
+    countImages: 0,
+
+    animationWidth: 0,
+    animationHeight: 0,
+
+    kenBurnsElement: null,
+    kenBurnsImages: null,
+
+    animationParams: {},
+
+
+    init: function () {
+        kenBurns.initElements();
+        kenBurns.initVariables();
+        kenBurns.resetUnusedImages(true);
+        kenBurns.doAnimation();
+        kenBurns.interval = window.setInterval(kenBurns.doAnimation, kenBurns.displayTime);
+    },
+
+
+    initElements: function () {
+        kenBurns.kenBurnsElement = $('.kenburns');
+        kenBurns.kenBurnsImages = $('img', kenBurns.kenBurnsElement);
+    },
+
+
+    initVariables: function () {
+        kenBurns.countImages = kenBurns.kenBurnsImages.length;
+        if (kenBurns.countImages === 0) {
+            return false;
+        }
+        kenBurns.animationWidth = kenBurns.kenBurnsElement.width();
+        kenBurns.animationHeight = keyVisualData.height * kenBurns.animationWidth / keyVisualData.width;
+        kenBurns.kenBurnsElement.css('height', kenBurns.animationHeight);
+        kenBurns.setNextImages();
+    },
+
+
+    setNextImages: function () {
+        ++kenBurns.actualImageID;
+        ++kenBurns.nextImageID;
+
+        if (kenBurns.actualImageID === kenBurns.countImages) {
+            kenBurns.actualImageID = 0;
+        }
+
+        if (kenBurns.nextImageID === kenBurns.countImages) {
+            kenBurns.nextImageID = 0;
+        }
+
+        kenBurns.actualImage = $(kenBurns.kenBurnsImages[kenBurns.actualImageID]);
+        kenBurns.nextImage = $(kenBurns.kenBurnsImages[kenBurns.nextImageID]);
+    },
+
+
+    doAnimation: function () {
+        if (kenBurns.countImages < 2) {
+            return false;
+        }
+
+        kenBurns.resetUnusedImages(false);
+        kenBurns.getAnimationParams();
+
+        kenBurns.setNextImages();
+    },
+
+
+    resetUnusedImages: function (doAll) {
+        kenBurns.kenBurnsImages.each(function (imageID, imageElement) {
+            var image = $(imageElement);
+
+            if ((imageID !== kenBurns.actualImageID && imageID !== kenBurns.nextImageID) || doAll === true) {
+
+                var imageWidth = kenBurns.animationWidth;
+                var imageHeight = kenBurns.animationHeight;
+                if (image.data('zoom') === 'out') {
+                    imageWidth = kenBurns.animationWidth * kenBurns.scaleFactor;
+                    imageHeight = kenBurns.animationHeight * kenBurns.scaleFactor;
+                }
+
+                switch (image.data('start')) {
+                    case 'nw' :
+                        image.css({left: 0, top: 0, right: '', bottom: ''});
+                        break;
+
+                    case 'n':
+                        image.css({left: (kenBurns.animationWidth - imageWidth) / 2, top: 0, right: '', bottom: ''});
+                        break;
+
+                    case 'ne':
+                        image.css({left: '', top: 0, right: 0, bottom: ''});
+                        break;
+
+                    case 'e':
+                        image.css({left: '', top: (kenBurns.animationHeight - imageHeight) / 2, right: 0, bottom: ''});
+                        break;
+
+                    case 'se':
+                        image.css({left: '', top: '', right: 0, bottom: 0});
+                        break;
+
+                    case 's' :
+                        image.css({left: (kenBurns.animationWidth - imageWidth) / 2, top: '', right: '', bottom: 0});
+                        break;
+
+                    case 'sw':
+                        image.css({left: 0, top: '', right: '', bottom: 0});
+                        break;
+
+                    case 'w':
+                        image.css({left: 0, top: (kenBurns.animationHeight - imageHeight) / 2, right: '', bottom: ''});
+                        break;
+
+                    case 'c':
+                        image.css({
+                            left: (kenBurns.animationWidth - imageWidth) / 2,
+                            top: (kenBurns.animationHeight - imageHeight) / 2,
+                            right: '',
+                            bottom: ''
+                        });
+                        break;
+                }
+
+                image.css({width: imageWidth, height: imageHeight, opacity: 0, zIndex: 0});
+            }
+
+
+        });
+    },
+
+
+    getAnimationParams: function () {
+        var cssObject = {};
+        var image = kenBurns.actualImage;
+        image.css('zIndex', 1);
+        cssObject.width = kenBurns.animationWidth;
+        cssObject.height = kenBurns.animationHeight;
+        if (image.data('zoom') === 'in') {
+            cssObject.width = kenBurns.animationWidth * kenBurns.scaleFactor;
+            cssObject.height = kenBurns.animationHeight * kenBurns.scaleFactor;
+        }
+
+        switch (image.data('start')) {
+            case 'n':
+                cssObject.left = (kenBurns.animationWidth - cssObject.width) / 2;
+                break;
+
+            case 'e':
+                cssObject.top = (kenBurns.animationHeight - cssObject.height) / 2;
+                break;
+
+            case 's' :
+                cssObject.left = (kenBurns.animationWidth - cssObject.width) / 2;
+                break;
+
+            case 'w':
+                cssObject.top = (kenBurns.animationHeight - cssObject.height) / 2;
+                break;
+
+            case 'c':
+                cssObject.left = (kenBurns.animationWidth - cssObject.width) / 2;
+                cssObject.top = (kenBurns.animationHeight - cssObject.height) / 2;
+                break;
+        }
+
+        cssObject.opacity = 4;
+
+        image.animate(
+            cssObject
+            , 7000, 'swing', function () {
+                image.animate({
+                    opacity: 0
+                }, 1000, 'swing')
+            })
+    }
+
+};
\ No newline at end of file
diff --git a/js/npm.js b/js/npm.js
deleted file mode 100644
index bf6aa80..0000000
--- a/js/npm.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
-require('../../js/transition.js')
-require('../../js/alert.js')
-require('../../js/button.js')
-require('../../js/carousel.js')
-require('../../js/collapse.js')
-require('../../js/dropdown.js')
-require('../../js/modal.js')
-require('../../js/tooltip.js')
-require('../../js/popover.js')
-require('../../js/scrollspy.js')
-require('../../js/tab.js')
-require('../../js/affix.js')
\ No newline at end of file
diff --git a/rendering/Controller/FrontendController.php b/rendering/Controller/FrontendController.php
index 359090a..14f29d5 100644
--- a/rendering/Controller/FrontendController.php
+++ b/rendering/Controller/FrontendController.php
@@ -10,6 +10,7 @@
 namespace Controller;
 
 use Helper\Database;
+use Helper\Registry;
 use Model\NavigationModel;
 use View\NavigationView;
 use View\StandardView;
@@ -45,19 +46,18 @@ class FrontendController
 		$navigation->setEditable($this->isEditable);
 		$navigation->init();
 
+		$registry = Registry::getInstance();
+		$registry->navID = NavigationModel::getActiveNavID();
+		$registry->navigationPath = NavigationModel::getNavigationPath();
+
 		$this->contents['title'] = NavigationModel::getTitle();
 		$this->contents['navigation'] = $navigation->render();
 
 		$this->contents['headline'] = NavigationModel::getHeadline();
-		$this->contents['navID'] = NavigationModel::getActiveNavID();
 		$this->contents['content'] = '';
-		$this->contents['keyVisual'] = '';
 
-		$tmpKeyVisual = NavigationModel::getKeyVisual();
-		foreach ($tmpKeyVisual as $data)
-		{
-			$this->contents['keyVisual'] .= $this->buildContents($data);
-		}
+		$keyVisual = NavigationModel::getKeyVisual();
+		$this->contents['keyVisual'] = $this->buildContents($keyVisual);
 
 		$tmpContents = NavigationModel::getContents();
 		/**
@@ -80,9 +80,18 @@ class FrontendController
 	 */
 	protected function buildContents(array $data)
 	{
+		/**
+		 * Abfangen von nicht behandelbaren Abfragen durch den mod_rewrite
+		 * z.B. nicht vorhandene Bilder
+		 */
+		if (!isset($data['Controller']))
+		{
+			return '';
+		}
 		/**
 		 * @var Database $modelClass
 		 */
+		$modelData = array();
 		$modelClass = 'Model\\' . $data['Controller'] . 'Model';
 		$viewClass = 'View\\' . $data['Controller'] . 'View';
 		if (class_exists($modelClass, true))
@@ -99,7 +108,6 @@ class FrontendController
 				$modelClass::setFilter($data['IDs']);
 				$modelData = $modelClass::getIndex();
 				$modelData = array_merge($modelData, $tmpModelData);
-				$modelData['navID'] = $this->contents['navID'];
 			}
 		}
 		else
@@ -109,6 +117,13 @@ class FrontendController
 			 */
 			return '';
 		}
+		if (empty($modelData))
+		{
+			/**
+			 * TODO: ErrorHandler bauen
+			 */
+			return '';
+		}
 		if (class_exists($viewClass, true))
 		{
 			$dataView = new $viewClass($modelData, $data['Controller']);
diff --git a/rendering/Helper/Database.php b/rendering/Helper/Database.php
index a822bd4..264e193 100644
--- a/rendering/Helper/Database.php
+++ b/rendering/Helper/Database.php
@@ -36,6 +36,19 @@ class Database
 	 */
 	static $filter = '1=1';
 
+	/**
+	 * Die Beziehung einer Datenbank-Tabelle zu einer anderen
+	 *     $relations = array(
+	 *         0 => array(
+	 *             'ownKey' => 'Spaltenname der eigenen Tabelle',
+	 *             'foreignTable' => 'Tabellenname der "Fremd"-Tabelle',
+	 *             'foreignKey' => 'Spaltenname der "Fremd"-Tabelle
+	 *         ),
+	 *     );
+	 * @var array
+	 */
+	static $relations = array();
+
 	/**
 	 * Hier werden die Daten von getIndex() gespeichert
 	 * @var array
@@ -48,18 +61,68 @@ class Database
 	protected static $db = null;
 
 
+	/**
+	 * Konstruktor schützen, weil Singleton
+	 * @author Christian Steinle
+	 */
 	protected function __construct()
 	{
 	}
 
-
-	protected function __clone()
+	/**
+	 * Liefert ein mehrdimensionales, assoziatives Array mit allen Datensätzen, die zum Filter passen
+	 *     als Schlüssel der ersten Dimension dient der Wert des Primär-Schlüssels der Datenbank-Tabelle
+	 * Legt alle Daten in self::$data ab
+	 * @author Christian Steinle
+	 *
+	 * @see self::query()
+	 * @see self::$data
+	 *
+	 * TODO: getIndex für static::$relations analog zu getItem
+	 *
+	 * @return array
+	 */
+	final public static function getIndex()
 	{
+		static::setRelations();
+		$sql = 'SELECT * FROM ' . static::TBL_NAME . ' WHERE ' . static::$filter . ' ' . static::ORDER_BY . ';';
+		self::$data = self::query($sql);
+		return self::$data;
 	}
 
+	/**
+	 * Setzt die Variable zur Verbindung verschiedener Datenbank-Tabellen
+	 */
+	public static function setRelations()
+	{
+		static::$relations = array();
+	}
+
+	/**
+	 * Liefert ein mehrdimensionales, assoziatives Array mit allen Datensätzen, die zum SQL-Query passen
+	 *     als Schlüssel der ersten Dimension dient der Wert des Primär-Schlüssels der Datenbank-Tabelle
+	 * @author Christian Steinle
+	 *
+	 * @param string $sql
+	 * @return array
+	 */
+	final protected static function query($sql)
+	{
+		self::getInstance();
+		$result = self::$db->query($sql);
+		$data = array();
+
+		while ($tmpData = $result->fetch_assoc())
+		{
+			$data[$tmpData[static::PRIMARY_KEY]] = $tmpData;
+		}
+
+		return $data;
+	}
 
 	/**
 	 * Stellt die Datenbank-Verbindung her
+	 * @author Christian Steinle
 	 *
 	 * return void
 	 */
@@ -72,56 +135,254 @@ class Database
 		}
 	}
 
+	/**
+	 * Macht das Update für den Eintrag in einer Datenbank-Tabelle
+	 * @author Christian Steinle
+	 *
+	 * @param array $request
+	 * @return int
+	 */
+	final public static function update(array $request)
+	{
+		static::beforeUpdate($request);
+		static::setRelations();
+
+		/**
+		 * @var Database $class
+		 */
+		foreach (static::$relations as $relation)
+		{
+			$class = $relation['foreignModel'];
+			$request[$relation['ownKey']] = $class::update($request);
+		}
+
+
+		$primaryKey = intval($request[static::PRIMARY_KEY]);
+		unset($request[static::PRIMARY_KEY]);
+
+		$model = self::queryModel(static::TBL_NAME, true);
+		$updateData = array();
+
+		foreach ($request as $fieldName => $fieldValue)
+		{
+			if (in_array($fieldName, $model))
+			{
+				$updateData[] = $fieldName . ' = "' . self::$db->real_escape_string($fieldValue) . '"';
+			}
+		}
+
+		if (empty($updateData))
+		{
+			return -1;
+		}
+
+		$sql = 'UPDATE ' . static::TBL_NAME . ' SET ' . implode(', ', $updateData) . ' WHERE ' . static::PRIMARY_KEY . ' = ' . $primaryKey . ' LIMIT 1;';
+		$result = self::$db->query($sql);
+		if ($result !== true)
+		{
+			return -1;
+		}
+
+		return $primaryKey;
+	}
+
+	/**
+	 * Liefert die Spalten einer Datenbank-Tabelle
+	 * @author Christian Steinle
+	 *
+	 * @param string $tableName
+	 * @param bool $fieldsOnly
+	 * @return array
+	 */
+	final protected static function queryModel($tableName, $fieldsOnly = false)
+	{
+		self::getInstance();
+		$sql = 'SHOW COLUMNS FROM ' . $tableName . ';';
+		$result = self::$db->query($sql);
+		$data = array();
+
+		while ($tmpData = $result->fetch_assoc())
+		{
+			if ($fieldsOnly === true)
+			{
+				$data[] = $tmpData['Field'];
+			}
+			else
+			{
+				$data[] = $tmpData;
+			}
+		}
+
+		return $data;
+	}
+
+	/**
+	 * Schreibt einen neuen Eintrag in einer Datenbank-Tabelle
+	 * @author Christian Steinle
+	 *
+	 * @param array $request
+	 * @return int
+	 */
+	final public static function insert(array $request)
+	{
+		static::beforeInsert($request);
+		static::setRelations();
+
+		/**
+		 * @var Database $class
+		 */
+		foreach (static::$relations as $relation)
+		{
+			$class = $relation['foreignModel'];
+			$request[$relation['ownKey']] = $class::insert($request);
+		}
+
+		$model = self::queryModel(static::TBL_NAME, true);
+		$updateData = array();
+
+		foreach ($request as $fieldName => $fieldValue)
+		{
+			if (in_array($fieldName, $model))
+			{
+				$updateData[$fieldName] = '"' . self::$db->real_escape_string($fieldValue) . '"';
+			}
+		}
+
+		if (empty($updateData))
+		{
+			return -1;
+		}
+
+		$keys = implode(', ', array_keys($updateData));
+		$values = implode(', ', $updateData);
+		$sql = 'INSERT INTO ' . static::TBL_NAME . ' (' . $keys . ') VALUES (' . $values . ');';
+		$result = self::$db->query($sql);
+
+		if ($result !== true)
+		{
+			return -1;
+		}
+
+		return self::$db->insert_id;
+	}
+
+	/**
+	 * Entfernt einen Eintrag aus einer Datenbank-Tabelle
+	 * @author Christian Steinle
+	 *
+	 * @param int $id
+	 * @return int
+	 */
+	final public static function delete($id)
+	{
+		static::beforeDelete($id);
+		static::setRelations();
+
+		if (!empty(static::$relations))
+		{
+			$data = static::getItem($id);
+
+			/**
+			 * @var Database $class
+			 */
+			foreach (static::$relations as $relation)
+			{
+				$class = $relation['foreignModel'];
+				$relationID = intval($data[$relation['foreignKey']]);
+				$class::delete($relationID);
+			}
+		}
+
+		$query = 'DELETE FROM ' . static::TBL_NAME . ' WHERE ' . static::PRIMARY_KEY . ' = ' . $id . ' LIMIT 1;';
+		$result = self::$db->query($query);
+
+		return (($result) ? 1 : 0);
+	}
 
 	/**
 	 * Liefert ein assoziatives Array des Datensatzes mit der übergebenen ID
+	 * @author Christian Steinle
 	 *
 	 * @param int $id
 	 * @return array
 	 */
 	final public static function getItem($id)
 	{
-		$sql = 'SELECT * FROM ' . static::TBL_NAME . ' WHERE ' . static::PRIMARY_KEY . ' = ' . $id . ';';
+		static::setRelations();
+
+		if (empty(static::$relations))
+		{
+			$sql = 'SELECT * FROM ' . static::TBL_NAME . ' WHERE ' . static::PRIMARY_KEY . ' = ' . $id . ';';
+		}
+		else
+		{
+			$tables = array(static::TBL_NAME);
+			$matches = array();
+			foreach (static::$relations as $relation)
+			{
+				$class = $relation['foreignModel'];
+				$tables[] = $class::TBL_NAME;
+				$matches[] = static::TBL_NAME . '.' . $relation['ownKey'] . ' = ' . $class::TBL_NAME . '.' . $relation['foreignKey'];
+			}
+
+			$sql = 'SELECT * FROM ' . implode(', ', $tables) . ' WHERE ' . static::PRIMARY_KEY . ' = ' . $id . ' AND ' . implode(' AND ', $matches);
+		}
+
 		$data = self::query($sql);
 		return (!is_array($data) || empty($data)) ? array() : current($data);
 	}
 
 
 	/**
-	 * Liefert ein mehrdimensionales, assoziatives Array mit allen Datensätzen, die zum Filter passen
-	 *     als Schlüssel der ersten Dimension dient der Wert des Primär-Schlüssels der Datenbank-Tabelle
-	 * Legt alle Daten in self::$data ab
+	 * Setzt den Filter für die Datenbank zur späteren Verwendung
+	 * @author Christian Steinle
 	 *
-	 * @see self::query()
-	 * @see self::$data
-	 *
-	 * @return array
+	 * @param string $filter
 	 */
-	final public static function getIndex()
+	public static function setFilter($filter)
 	{
-		$sql = 'SELECT * FROM ' . static::TBL_NAME . ' WHERE ' . static::$filter . ' ' . static::ORDER_BY . ';';
-		self::$data = self::query($sql);
-		return self::$data;
+		static::$filter = $filter;
 	}
 
 
 	/**
-	 * Liefert ein mehrdimensionales, assoziatives Array mit allen Datensätzen, die zum SQL-Query passen
-	 *     als Schlüssel der ersten Dimension dient der Wert des Primär-Schlüssels der Datenbank-Tabelle
+	 * Funktion, die vor dem Erstellen eines Datenbank-Eintrags ausgeführt wird
+	 * @author Christian Steinle
 	 *
-	 * @param string $sql
-	 * @return array
+	 * @param array $request
 	 */
-	final protected static function query($sql)
+	protected static function beforeInsert(array &$request)
 	{
-		self::getInstance();
-		$result = self::$db->query($sql);
-		$data = array();
-		while ($tmpData = $result->fetch_assoc())
-		{
-			$data[$tmpData[static::PRIMARY_KEY]] = $tmpData;
-		}
+	}
 
-		return $data;
+
+	/**
+	 * Funktion, die vor dem Löschen eines Datenbank-Eintrags ausgeführt wird
+	 * @author Christian Steinle
+	 *
+	 * @param int $id
+	 */
+	protected static function beforeDelete($id)
+	{
+	}
+
+
+	/**
+	 * Funktion, die vor dem Update eines Datenbank-Eintrags ausgeführt wird
+	 * @author Christian Steinle
+	 *
+	 * @param array $request
+	 */
+	protected static function beforeUpdate(array &$request)
+	{
+	}
+
+
+	/**
+	 * Schützen, um das Klonen des Singletons zu vermeiden
+	 * @author Christian Steinle
+	 */
+	final protected function __clone()
+	{
 	}
 }
\ No newline at end of file
diff --git a/rendering/Helper/Text.php b/rendering/Helper/Text.php
new file mode 100644
index 0000000..2e01ef0
--- /dev/null
+++ b/rendering/Helper/Text.php
@@ -0,0 +1,23 @@
+/', '', $text, 1));
+		$text = preg_replace('/>p\/editorConfig['mediaPath'] . $navID . '/';
+		if (!is_dir($mediaPath))
+		{
+			mkdir($mediaPath, 0775, true);
+		}
+
+		ImageModel::createOrigImage($mediaPath, $request);
+		ImageModel::createWebImage($mediaPath, $request, 'keyVisual');
+	}
+
+	/**
+	 * Erzeugt einen Unique-Filename in einem vorgegebenen Pfad
+	 * @author Christian Steinle
+	 * @param string $path
+	 * @param string $filename
+	 * @return string
+	 */
+	public static function getUniqueFilename($path, $filename)
+	{
+		$filenameArray = self::filenameToArray($filename);
+		$filesInPath = glob($path . $filenameArray['filename'] . '*_orig.' . $filenameArray['extension']);
+		if (count($filesInPath) === 0)
+		{
+			return $filenameArray['filename'];
+		}
+		else
+		{
+			$newFilename = $filenameArray['filename'] . '_' . count($filesInPath);
+			return $newFilename;
+		}
+	}
+
+	/**
+	 * Liefert ein Array zum übergebenen Dateinamen
+	 * - 'filename' => Dateiname ohne Erweiterung
+	 * - 'extension' => Datei-Erweiterung
+	 * @author Christian Steinle
+	 *
+	 * @param string $filename
+	 * @return array
+	 */
+	public static function filenameToArray($filename)
+	{
+		$tmpData = explode('.', $filename, -1);
+		$newFilename = implode('.', $tmpData);
+		$extension = str_replace($newFilename . '.', '', $filename);
+		return array('filename' => $newFilename, 'extension' => $extension);
+	}
+
+	/**
+	 * Kopiert das Standard-Bild oder -KeyVisual
+	 * @author Christian Steinle
+	 *
+	 * @param string $newPath
+	 * @param string $filename
+	 * @param string $type
+	 */
+	public static function copyStandardImage($newPath, $filename, $type)
+	{
+		$registry = Registry::getInstance();
+		switch ($type)
+		{
+			case 'image':
+				$imageData = $registry->editorConfig['imageDimension']['image'];
+				break;
+
+			case 'keyVisual':
+				$imageData = $registry->editorConfig['imageDimension']['keyVisual'];
+				break;
+
+			default:
+				return;
+		}
+
+		if (!is_dir($newPath))
+		{
+			mkdir($newPath, 0777, true);
+		}
+
+		$standardImagePath = substr(str_replace(PATH_PREFIX, '', PATH_ROOT), 0, -1) . $imageData['standardImage'];
+		$filenameArray = self::filenameToArray($filename);
+
+		copy($standardImagePath, $newPath . $filename);
+		copy($standardImagePath, $newPath . $filenameArray['filename'] . '_orig.' . $filenameArray['extension']);
+		self::createThumb($newPath, $filename);
+	}
+
+	/**
+	 * Erstellt ein Thumbnail von einem Bild
+	 * @author Christian Steinle
+	 *
+	 * @param string $path
+	 * @param string $file
+	 */
+	public static function createThumb($path, $file)
+	{
+		$registry = Registry::getInstance();
+		$imageData = $registry->editorConfig['imageDimension']['thumb'];
+		$newWidth = $imageData['width'];
+		$newQuality = $imageData['quality'];
+		$filenameArray = self::filenameToArray($file);
+
+		list($width, $height) = getimagesize($path . $file);
+		$newHeight = $height * $newWidth / $width;
+
+		$newImage = imagecreatetruecolor($newWidth, $newHeight);
+		switch ($filenameArray['extension'])
+		{
+			case 'jpg':
+				$origImage = imagecreatefromjpeg($path . $file);
+				imagecopyresampled($newImage, $origImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+				imagejpeg($newImage, $path . $filenameArray['filename'] . '_thumb.jpg', $newQuality);
+				break;
+
+			case 'png':
+				$origImage = imagecreatefrompng($path . $file);
+				imagecopyresampled($newImage, $origImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+				imagepng($newImage, $path . $filenameArray['filename'] . '_thumb.png');
+				break;
+
+			case 'gif':
+				$origImage = imagecreatefromgif($path . $file);
+				imagecopyresampled($newImage, $origImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+				imagegif($newImage, $path . $filenameArray['filename'] . '_thumb.gif');
+				break;
+
+			default:
+				return;
+		}
+	}
+
+	/**
+	 * Löscht vorhandene Bilder vor entfernen aus der Datenbank
+	 * @author Christian Steinle
+	 *
+	 * @param int $id
+	 */
+	protected static function beforeDelete($id)
+	{
+		$registry = Registry::getInstance();
+		$data = self::getItem($id);
+
+		$mediaPath = $registry->editorConfig['mediaPath'] . $registry->navID . '/';
+		ImageModel::deleteImages($mediaPath, $data['imageName'], $data['imageExtension']);
+	}
+
+	/**
+	 * Löscht die Bilder aus dem Medienpfad
+	 * @author Christian Steinle
+	 *
+	 * @param $mediaPath
+	 * @param $imageName
+	 * @param $imageExtension
+	 */
+	public static function deleteImages($mediaPath, $imageName, $imageExtension)
+	{
+		unlink($mediaPath . $imageName . '.' . $imageExtension);
+		unlink($mediaPath . $imageName . '_orig.' . $imageExtension);
+		unlink($mediaPath . $imageName . '_thumb.' . $imageExtension);
+	}
+
+	/**
+	 * Verschiebt / Erzeugt Bilder und generiert Derivate
+	 * @author Christian Steinle
+	 *
+	 * @param array $request
+	 */
+	protected static function beforeUpdate(array &$request)
+	{
+		$registry = Registry::getInstance();
+		$mediaPath = $registry->editorConfig['mediaPath'] . $registry->navID . '/';
+
+		$imageType = 'image';
+		if (isset($request['keyVisualID']))
+		{
+			$imageType = 'keyVisual';
+		}
+
+		$oldData = self::getItem($request[static::PRIMARY_KEY]);
+
+		if (isset($_FILES['changeUploadFile']) && $_FILES['changeUploadFile']['tmp_name'] !== '' && $_FILES['changeUploadFile']['error'] === 0)
+		{
+			ImageModel::deleteImages($mediaPath, $oldData['imageName'], $oldData['imageExtension']);
+			$request['imageName'] = ImageModel::getUniqueFilename($mediaPath, $request['imageName'] . '.' . $request['imageExtension']);
+			ImageModel::createOrigImage($mediaPath, $request);
+			ImageModel::createWebImage($mediaPath, $request, $imageType);
+		}
+		elseif (isset($_FILES['changeUploadFile']) && $_FILES['changeUploadFile']['tmp_name'] === '' && $oldData['imageName'] !== $request['imageName'])
+		{
+			$request['imageName'] = ImageModel::getUniqueFilename($mediaPath, $request['imageName'] . '.' . $request['imageExtension']);
+			rename($mediaPath . $oldData['imageName'] . '_orig.' . $oldData['imageExtension'], $mediaPath . $request['imageName'] . '_orig.' . $request['imageExtension']);
+			rename($mediaPath . $oldData['imageName'] . '.' . $oldData['imageExtension'], $mediaPath . $request['imageName'] . '.' . $request['imageExtension']);
+			rename($mediaPath . $oldData['imageName'] . '_thumb.' . $oldData['imageExtension'], $mediaPath . $request['imageName'] . '_thumb.' . $request['imageExtension']);
+		}
+
+		if (isset($_FILES['changeUploadFile']) &&
+			$_FILES['changeUploadFile']['tmp_name'] === '' &&
+			(abs($oldData['imageTop'] - $request['imageTop']) > .5 ||
+				abs($oldData['imageLeft'] - $request['imageLeft']) > .5 ||
+				abs($oldData['imageHeight'] - $request['imageHeight']) > .5 ||
+				abs($oldData['imageWidth'] - $request['imageWidth']) > .5)
+		)
+		{
+			unlink($mediaPath . $request['imageName'] . '.' . $request['imageExtension']);
+			unlink($mediaPath . $request['imageName'] . '_thumb.' . $request['imageExtension']);
+			ImageModel::createWebImage($mediaPath, $request, $imageType);
+		}
+	}
+
+	/**
+	 * Legt ein hochgeladenes Bild im Medienpfad ab und verkleinert es, falls die Abmessungen zu groß sind
+	 * @author Christian Steinle
+	 *
+	 * @param $mediaPath
+	 * @param $request
+	 */
+	public static function createOrigImage($mediaPath, &$request)
+	{
+		$registry = Registry::getInstance();
+		$origData = $registry->editorConfig['imageDimension']['orig'];
+
+		$tmpName = $request['imageName'] . '__tmp__.' . $request['imageExtension'];
+
+		if (!is_dir($mediaPath))
+		{
+			mkdir($mediaPath, 0777, true);
+		}
+
+		move_uploaded_file($_FILES['changeUploadFile']['tmp_name'], $mediaPath . $tmpName);
+
+		list($width, $height) = getimagesize($mediaPath . $tmpName);
+		if ($width > $origData['width'] || $height > $origData['height'])
+		{
+			$newWidth = $origData['width'];
+			$newHeight = $height * $newWidth / $width;
+
+			$newImage = imagecreatetruecolor($newWidth, $newHeight);
+			switch ($request['imageExtension'])
+			{
+				case 'jpg':
+					$origImage = imagecreatefromjpeg($mediaPath . $tmpName);
+					imagecopyresampled($newImage, $origImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+					imagejpeg($newImage, $mediaPath . $request['imageName'] . '_orig.jpg', $origData['quality']);
+					break;
+
+				case 'png':
+					$origImage = imagecreatefrompng($mediaPath . $tmpName);
+					imagecopyresampled($newImage, $origImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+					imagepng($newImage, $mediaPath . $request['imageName'] . '_orig.png');
+					break;
+
+				case 'gif':
+					$origImage = imagecreatefromgif($mediaPath . $tmpName);
+					imagecopyresampled($newImage, $origImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+					imagegif($newImage, $mediaPath . $request['imageName'] . '_orig.gif');
+					break;
+
+				default:
+					return;
+			}
+
+			unlink($mediaPath . $tmpName);
+		}
+		else
+		{
+			rename($mediaPath . $tmpName, $mediaPath . $request['imageName'] . '_orig.' . $request['imageExtension']);
+		}
+	}
+
+	/**
+	 * Erzeugt das WebImage mit dem passenden Ausschnitt aus dem Original
+	 * @author Christian Steinle
+	 * TODO: Transparenz bei pngs
+	 *
+	 * @param $mediaPath
+	 * @param $request
+	 * @param $type
+	 */
+	public static function createWebImage($mediaPath, $request, $type)
+	{
+		$registry = Registry::getInstance();
+		switch ($type)
+		{
+			case 'image':
+				$imageData = $registry->editorConfig['imageDimension']['image'];
+				break;
+
+			case 'keyVisual':
+				$imageData = $registry->editorConfig['imageDimension']['keyVisual'];
+				break;
+
+			default:
+				return;
+		}
+
+		$origName = $request['imageName'] . '_orig.' . $request['imageExtension'];
+		$webName = $request['imageName'] . '.' . $request['imageExtension'];
+		list($width, $height) = getimagesize($mediaPath . $origName);
+		$newWidth = $imageData['width'];
+		$newHeight = $newWidth * $request['imageHeight'] / $request['imageWidth'];
+
+		$newImage = imagecreatetruecolor($newWidth, $newHeight);
+		switch ($request['imageExtension'])
+		{
+			case 'jpg':
+				$origImage = imagecreatefromjpeg($mediaPath . $origName);
+				imagecopyresampled($newImage, $origImage, 0, 0, $request['imageLeft'] * $width / $newWidth, $request['imageTop'] * $width / $newWidth, $newWidth, $newHeight, $width * $request['imageWidth'] / $newWidth, $width * $request['imageWidth'] * $newHeight / ($newWidth * $newWidth));
+				imagejpeg($newImage, $mediaPath . $webName, $imageData['quality']);
+				break;
+
+			case 'png':
+				$origImage = imagecreatefrompng($mediaPath . $origName);
+				imagecopyresampled($newImage, $origImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+				imagepng($newImage, $mediaPath . $webName);
+				break;
+
+			case 'gif':
+				$origImage = imagecreatefromgif($mediaPath . $origName);
+				imagecopyresampled($newImage, $origImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
+				imagegif($newImage, $mediaPath . $webName);
+				break;
+
+			default:
+				return;
+		}
+
+		self::createThumb($mediaPath, $webName);
+	}
 }
\ No newline at end of file
diff --git a/rendering/Model/ImageTextModel.php b/rendering/Model/ImageTextModel.php
index dae5c40..6201fc5 100644
--- a/rendering/Model/ImageTextModel.php
+++ b/rendering/Model/ImageTextModel.php
@@ -11,10 +11,65 @@ namespace Model;
 
 
 use Helper\Database;
+use Helper\Registry;
 
 class ImageTextModel extends Database
 {
 	const TBL_NAME = TBL_IMAGE_TEXT;
 
-	const PRIMARY_KEY = 'ID';
+	const PRIMARY_KEY = 'imageTextID';
+
+
+	/**
+	 * Setzt die Relationen zu anderen Modellen
+	 * @author Christian Steinle
+	 */
+	public static function setRelations()
+	{
+		static::$relations = array(
+			0 => array(
+				'ownKey' => 'imageID',
+				'foreignModel' => 'Model\\ImageModel',
+				'foreignKey' => 'imageID'
+			)
+		);
+	}
+
+
+	/**
+	 * Erzeugt die benötigten Bilder im Medien-Pfad und bereitet den Request auf
+	 * @author Christian Steinle
+	 *
+	 * @param array $request
+	 */
+	protected static function beforeInsert(array &$request)
+	{
+		$registry = Registry::getInstance();
+		$imageData = $registry->editorConfig['imageDimension']['image'];
+		$standardImagePath = substr(str_replace(PATH_PREFIX, '', PATH_ROOT), 0, -1) . $imageData['standardImage'];
+		$navID = $request['navID'];
+		unset($request['navID']);
+
+		$mediaPath = $registry->editorConfig['mediaPath'] . $navID . '/';
+		if (!is_dir($mediaPath))
+		{
+			mkdir($mediaPath, 0775, true);
+		}
+
+		$filename = end(explode('/', $standardImagePath));
+		$newFilename = ImageModel::getUniqueFilename($mediaPath, $filename);
+		$fileArray = ImageModel::filenameToArray($filename);
+		$request['imageOrigName'] = $fileArray['filename'];
+		$request['imageExtension'] = $fileArray['extension'];
+		$request['imageName'] = $newFilename;
+		list($width, $height) = getimagesize($standardImagePath);
+		$request['imageTop'] = 0;
+		$request['imageLeft'] = 0;
+		$request['imageHeight'] = $height;
+		$request['imageWidth'] = $width;
+		$request['imageTextModel'] = true;
+		ImageModel::copyStandardImage($mediaPath, $newFilename . '.' . $fileArray['extension'], 'image');
+	}
+
+
 }
\ No newline at end of file
diff --git a/rendering/Model/NavigationModel.php b/rendering/Model/NavigationModel.php
index 3bd07fa..aa33818 100644
--- a/rendering/Model/NavigationModel.php
+++ b/rendering/Model/NavigationModel.php
@@ -12,6 +12,7 @@ namespace Model;
 
 
 use Helper\Database;
+use Helper\Registry;
 
 class NavigationModel extends Database
 {
@@ -39,6 +40,8 @@ class NavigationModel extends Database
 
 	/**
 	 * Setzt später benötigte Variablen für den FrontendController, so dass die Datenmodelle und die Views instanziiert werden können
+	 * @author Christian Steinle
+	 *
 	 * @param array $routeParts
 	 * @param bool $isEditable
 	 */
@@ -56,6 +59,13 @@ class NavigationModel extends Database
 	}
 
 
+	/**
+	 * Initialisierung für das Backend ohne ModRewrite
+	 * @author Christian Steinle
+	 *
+	 * @param int $siteID
+	 * @param int $depth
+	 */
 	protected static function initBackend($siteID, $depth)
 	{
 		foreach (self::$data as $navID => $navItem)
@@ -74,6 +84,9 @@ class NavigationModel extends Database
 	}
 
 	/**
+	 * Umbau der Datenbank-Tabellenwerte in ein Mehrdimensionales Array
+	 * @author Christian Steinle
+	 *
 	 * @param string $navKeyVisual
 	 */
 	protected static function initKeyVisual($navKeyVisual)
@@ -89,6 +102,9 @@ class NavigationModel extends Database
 	}
 
 	/**
+	 * Umbau der Datenbank-Tabellenwerte in ein Mehrdimensionales Array
+	 * @author Christian Steinle
+	 *
 	 * @param string $navContents
 	 */
 	protected static function initContent($navContents)
@@ -103,6 +119,11 @@ class NavigationModel extends Database
 		}
 	}
 
+
+	/**
+	 * Baut den Navigationspfad als Array
+	 * @author Christian Steinle
+	 */
 	protected static function buildNavigationPath()
 	{
 		$navStart = $navStartPath[] = self::$activeNavID;
@@ -110,13 +131,22 @@ class NavigationModel extends Database
 		{
 			$navStart = $navStartPath[] = self::$data[$navStart]['navStart'];
 		}
+		$navStartPath[] = '0';
 		array_pop($navStartPath);
 		self::$navigationPath = array_reverse($navStartPath);
 	}
 
+
+	/**
+	 * Initialisiert das Frontend unter Verwendung des ModRewrites
+	 * @author Christian Steinle
+	 *
+	 * @param array $routeParts
+	 */
 	protected static function initFrontend(array $routeParts)
 	{
 		$navStart = 0;
+		self::$navigationPath[] = 0;
 		foreach ($routeParts as $navLink)
 		{
 			foreach (self::$data as $navID => $navItem)
@@ -134,7 +164,10 @@ class NavigationModel extends Database
 		}
 	}
 
+
 	/**
+	 * Liefert den Navigationspfad als Array
+	 *
 	 * @return array
 	 */
 	public static function getNavigationPath()
@@ -144,6 +177,8 @@ class NavigationModel extends Database
 
 
 	/**
+	 * Liefert die NavigationsID des aktuellen gewählten Menüpunkts
+	 *
 	 * @return int
 	 */
 	public static function getActiveNavID()
@@ -153,6 +188,7 @@ class NavigationModel extends Database
 
 
 	/**
+	 * Liefert den Seiten-Titel
 	 * @return string
 	 */
 	public static function getTitle()
@@ -162,6 +198,7 @@ class NavigationModel extends Database
 
 
 	/**
+	 * Liefert das Array für das KeyVisual
 	 * @return array
 	 */
 	public static function getKeyVisual()
@@ -171,6 +208,7 @@ class NavigationModel extends Database
 
 
 	/**
+	 * Liefert die Überschrift der Seite
 	 * @return string
 	 */
 	public static function getHeadline()
@@ -180,6 +218,7 @@ class NavigationModel extends Database
 
 
 	/**
+	 * Liefert die Seiteninhalte
 	 * @return array
 	 */
 	public static function getContents()
@@ -189,10 +228,145 @@ class NavigationModel extends Database
 
 
 	/**
+	 * Liefert die Navigationsdaten als flaches Array
 	 * @return array
 	 */
 	public static function getData()
 	{
 		return self::$data;
 	}
+
+
+	/**
+	 * Liefert die Navigationsdaten, sortiert nach ihrem Elternknoten
+	 * @return array
+	 */
+	public static function getStructuredIndex()
+	{
+		$tmpData = self::getIndex();
+		$structuredData = array();
+		foreach ($tmpData as $navID => $navData)
+		{
+			$structuredData['navStart_' . $navData['navStart']]['navID_' . $navID] = $navData;
+		}
+		return $structuredData;
+	}
+
+
+	/**
+	 * Liefert einen Link, der innerhalb des NavStarts unique ist
+	 * @author Christian Steinle
+	 *
+	 * @param int $navStart
+	 * @param string $navName
+	 * @return string
+	 */
+	public static function getUniqueNavLink($navStart, $navName)
+	{
+		$commonReplacements = array('ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss', ' ' => '_', '"', '', '\'' => '');
+		$registry = Registry::getInstance();
+		$replacements = $registry->editorConfig['linkReplacements'];
+		$navLink = trim(str_replace($replacements, '', $navName));
+		$navLink = mb_convert_case($navLink, MB_CASE_LOWER);
+		$navLink = str_replace(array_keys($commonReplacements), array_values($commonReplacements), $navLink);
+
+		self::$filter = 'navStart = ' . $navStart;
+		$existingNavLinks = 0;
+		$tmpData = self::getIndex();
+
+		foreach ($tmpData as $navPoint)
+		{
+			if ($navPoint['navLink'] == $navLink)
+			{
+				++$existingNavLinks;
+			}
+		}
+
+		if ($existingNavLinks === 0)
+		{
+			return $navLink;
+		}
+		return $navLink . '_' . $existingNavLinks;
+	}
+
+
+	/**
+	 * Sortiert die Navigationspunkte eines übergebenen Elternelements
+	 * @author Christian Steinle
+	 *
+	 * @param array $navPointData
+	 * @return int
+	 */
+	public static function updateSortAfterInsert(array $navPointData)
+	{
+		if (!isset($navPointData['navStart']) || $navPointData['navStart'] === '')
+		{
+			return -1;
+		}
+
+		self::$filter = 'navStart = ' . $navPointData['navStart'];
+		$sortOrder = $navPointData['navSort'];
+		$tmpData = self::getIndex();
+		unset($tmpData[$navPointData['navID']]);
+		if ($sortOrder != 0)
+		{
+			array_splice($tmpData, 0, $sortOrder);
+		}
+
+		foreach($tmpData as $navData)
+		{
+			++$sortOrder;
+			self::update(array('navID' => $navData['navID'], 'navSort' => $sortOrder));
+		}
+
+		return 1;
+	}
+
+
+	/**
+	 * Sortiert die Navigationspunkte eines Elternelements um
+	 * @author Christian Steinle
+	 *
+	 * @param $sortData
+	 * @return int
+	 */
+	public static function updateSort($sortData)
+	{
+		foreach($sortData as $navSort => $navID)
+		{
+			self::update(array('navID' => $navID, 'navSort' => $navSort));
+		}
+
+		return 1;
+	}
+
+
+	/**
+	 * Legt die Bilddaten des Keyvisuals an, und liefert den passenden JSON-String für den Eintrag in die Datenbank-Tabelle
+	 * @author Christian Steinle
+	 *
+	 * @param array $request
+	 */
+	protected static function beforeInsert(&$request)
+	{
+		$registry = Registry::getInstance();
+		$imageData = $registry->editorConfig['imageDimension']['keyVisual'];
+		$standardImagePath = substr(str_replace(PATH_PREFIX, '', PATH_ROOT), 0, -1) . $imageData['standardImage'];
+
+		$filename = end(explode('/', $standardImagePath));
+		$fileArray = ImageModel::filenameToArray($filename);
+		$imageData = array();
+		$imageData['imageOrigName'] = $fileArray['filename'];
+		$imageData['imageExtension'] = $fileArray['extension'];
+		$imageData['imageName'] = $fileArray['filename'];
+		$imageData['imageTitle'] = $fileArray['filename'];
+		list($width, $height) = getimagesize($standardImagePath);
+		$imageData['imageTop'] = 0;
+		$imageData['imageLeft'] = 0;
+		$imageData['imageHeight'] = $height;
+		$imageData['imageWidth'] = $width;
+
+		$imageID = ImageModel::insert($imageData);
+		$request['navKeyVisual'] = json_encode(array('Controller' => 'KeyVisual', 'Type' => 'Single', 'IDs' => array($imageID)));
+	}
 }
\ No newline at end of file
diff --git a/rendering/Model/SublineModel.php b/rendering/Model/SublineModel.php
new file mode 100644
index 0000000..96dda99
--- /dev/null
+++ b/rendering/Model/SublineModel.php
@@ -0,0 +1,23 @@
+isEditable)
 			{
-				$this->navigationHtml .= '' . $navItem['navName'] . $span . '';
+				$link = $linkPrefix . '/index.php?siteID=' . $navItem['navID'];
+				$target = '';
+				if($navItem['navType'] === 'material')
+				{
+					$registry = Registry::getInstance();
+					$link = $registry->editorConfig['mediaPrefix'] . $navItem['navID'] . '/' . $navItem['navLink'];
+					$target = ' target="_blank"';
+				}
+				$this->navigationHtml .= '' . $navItem['navName'] . $span . '';
 			}
 			else
 			{
-				$this->navigationHtml .= '' . $navItem['navName'] . $span . '';
+				$link = $linkPrefix . '/' . $navItem['navLink'];
+				$target = '';
+				if($navItem['navType'] === 'material')
+				{
+					$registry = Registry::getInstance();
+					$link = $registry->editorConfig['mediaPrefix'] . $navItem['navID'] . '/' . $navItem['navLink'];
+					$target = ' target="_blank"';
+				}
+				$this->navigationHtml .= '' . $navItem['navName'] . $span . '';
 			}
 			if (isset($this->orderedNavigation[$navItem['navID']]))
 			{
diff --git a/rendering/View/StandardView.php b/rendering/View/StandardView.php
index e31f199..6f8f03a 100644
--- a/rendering/View/StandardView.php
+++ b/rendering/View/StandardView.php
@@ -82,7 +82,7 @@ class StandardView
 	public function render()
 	{
 		ob_start();
-		include_once($this->template);
+		include($this->template);
 
 		$templateContent = ob_get_contents();
 		ob_end_clean();
diff --git a/templates/imagetext.phtml b/templates/imagetext.phtml
index a1dbddd..b0ca959 100644
--- a/templates/imagetext.phtml
+++ b/templates/imagetext.phtml
@@ -1,24 +1,27 @@
 data['type'] === 'small')
-	{
-		$imageClass = 'col-md-4';
-		$textClass = 'col-md-8';
-	}
-	else
-	{
-		$imageClass = 'col-md-8';
-		$textClass = 'col-md-4';
-	}
+$registry = Helper\Registry::getInstance();
+if ($this->data['type'] === 'small')
+{
+	$imageClass = 'col-md-4';
+	$textClass = 'col-md-8';
+}
+else
+{
+	$imageClass = 'col-md-8';
+	$textClass = 'col-md-4';
+}
+
+$editorData = '';
+if ($this->isEditable === true)
+{
+	$editorData = ' data-editor="ImageText" data-id="' . $this->data['imageTextID'] . '" data-element="ImageText_' . $this->data['imageTextID'] . '"';
+}
 
-	$editorData = '';
-	if ($this->isEditable === true)
-	{
-		$editorData = ' data-editor="ImageText" data-id="' . $this->data['ID'] . '" data-element="ImageText_' . $this->data['ID'] . '"';
-	}
 ?>
-
 
> -
+
+ +

data['text']); ?>

diff --git a/templates/index.phtml b/templates/index.phtml index 5cd6feb..8e9fc92 100644 --- a/templates/index.phtml +++ b/templates/index.phtml @@ -8,18 +8,25 @@ + + + isEditable) { - $registry = \Helper\Registry::getInstance(); - $registry->editorConfig['navID'] = $this->data['navID']; + $registry->editorConfig['navID'] = $registry->navID; + $registry->editorConfig['navigationPath'] = $registry->navigationPath; ?> + + - -
- data['keyVisual']; - $editorData = ''; - if ($this->isEditable === true) - { - $editorData = ' data-editor="Headline" data-id="' . $this->data['navID'] . '" data-element="Headline_' . $this->data['navID'] . '"'; - } - ?> -
>

data['headline']; ?>

-
- data['content']; ?> -
+
- \ No newline at end of file diff --git a/templates/keyvisual.phtml b/templates/keyvisual.phtml index 2538fe1..b63c66f 100644 --- a/templates/keyvisual.phtml +++ b/templates/keyvisual.phtml @@ -2,70 +2,79 @@ if (isset($this->data['Type'])) { + $registry = Helper\Registry::getInstance(); + $mediaPath = $registry->editorConfig['mediaPrefix'] . $registry->navID . '/'; + $editorData = ''; if ($this->isEditable === true) { - $editorData = ' data-editor="' . $this->data['Type'] . '" data-id="' . $this->data['navID'] . '" data-element="' . $this->data['Type'] . '_' . $this->data['navID'] . '"'; + $editorData = ' data-editor="KeyVisual" data-id="' . $registry->navID . '" data-element="' . $this->data['Type'] . '_' . $registry->navID . '"'; } - if ($this->data['Type'] === 'slider') - { - ?> -
> -
- data as $key => $data) - { - if (!is_numeric($key)) - { - continue; - } - else - { - ?> - - -
-
- data['Type'] === 'kenburns') - { - ?> -
> -
- data as $key => $data) - { - if (!is_numeric($key)) - { - continue; - } - else - { - ?> - - -
-
- data['Type'] === 'single') + if ($this->data['Type'] === 'Slider') { ?>
> -
- +
+ +
+
+ data['Type'] === 'Kenburns') + { + ?> +
> +
+ +
+
+ data['Type'] === 'Single') + { + ?> +
> +
+
isEditable === true) +{ + $editorData = ' data-editor="Subline" data-id="' . $this->data['sublineID'] . '" data-element="Subline_' . $this->data['sublineID'] . '"'; +} + +?> +
> +
+

data['text']; ?>

+
+
\ No newline at end of file diff --git a/templates/text.phtml b/templates/text.phtml new file mode 100644 index 0000000..a136595 --- /dev/null +++ b/templates/text.phtml @@ -0,0 +1,14 @@ +isEditable === true) +{ + $editorData = ' data-editor="Text" data-id="' . $this->data['textID'] . '" data-element="Text_' . $this->data['textID'] . '"'; +} + +?> +
> +
+

data['text']; ?>

+
+
\ No newline at end of file