Moritz Schmidt 10 yıl önce
ebeveyn
işleme
3bd597d788

BIN
img/moeflix.png


+ 11 - 0
inc/config.inc.php.dist

@@ -0,0 +1,11 @@
+<?php
+
+$CONF = array(
+  "dbHost" => "127.0.0.1",
+  "dbUser" => "user",
+  "dbPass" => "pass",
+  "dbName" => "dbName",
+
+);
+
+?>

+ 150 - 0
inc/controller.php

@@ -0,0 +1,150 @@
+<?php
+class Controller {
+
+	private $request = null;
+	private $template = '';
+	private $view = null;
+	private $scraper = null;
+
+	/**
+	 * Konstruktor, erstellet den Controller.
+	 *
+	 * @param Array $request Array aus $_GET & $_POST.
+	 */
+	public function __construct($request) {
+		$this->view = new View();
+		$this->request = $request;
+		$this->template = !empty($request['view']) ? $request['view'] : 'default';
+		$GLOBALS['db'] = new Database($GLOBALS['conf']);
+
+		if(!empty($request['action']) && $request['action'] === "scrape") {
+			$source = $GLOBALS['db']->getAllAssoc("sources", "id", $request['sourceID']);
+
+			if($source[0]['type'] == 0) { // Series
+				$this->scraper = new SeriesScraper();
+				$this->scraper->scrapeFolder($request['sourceID']);
+			} else {
+				$this->scraper = new MovieScraper();
+				$this->scraper->scrapeFolder($request['sourceID']);
+			}
+		}
+
+		if(!empty($request['action']) && $request['action'] === "scrapeSingleTV") {
+			$this->scraper = new SeriesScraper();
+			$this->scraper->downloadSeriesByID($request['moviedbID'], urldecode($request['path']), $request['sourceID']);
+		}
+
+		if(!empty($request['action']) && $request['action'] === "scrapeSingleMovie") {
+			$this->scraper = new MovieScraper();
+			$this->scraper->downloadMovieByID($request['moviedbID'], urldecode($request['path']), $request['sourceID']);
+		}
+
+		if(!empty($request['action']) && $request['action'] === "login") {
+			User::login($request);
+		}
+
+		if(!empty($request['action']) && $request['action'] === "logout") {
+			User::logout();
+		}
+
+		if(!empty($request['action']) && $request['action'] === "removeSeries") {
+			SeriesScraper::remove($request['seriesID']);
+		}
+
+		if(!empty($request['action']) && $request['action'] === "updateSeries") {
+			$this->scraper = new SeriesScraper();
+			$this->scraper->update($request['seriesID']);
+		}
+
+		if(!empty($request['action']) && $request['action'] === "updateUserInfo") {
+			User::update($request['newPassword'], $request['newPasswordConfirmation'], $request['newEmail'], $request['oldEmail']);
+		}
+
+		if(!empty($request['action']) && $request['action'] === "invite") {
+			User::invite($request['inviteMail']);
+		}
+
+		if(!empty($_SESSION['user'])) {
+			$GLOBALS['user'] = unserialize($_SESSION['user']);
+		}
+
+		if(empty($_SESSION['user']) && !empty($_SESSION['loggedIn']) && $_SESSION['loggedIn']) {
+			$_SESSION['user'] = serialize(new User($_SESSION['mail']));
+		}
+
+	}
+
+	/**
+	 * Methode zum anzeigen des Contents.
+	 *
+	 * @return String Content der Applikation.
+	 */
+	public function display() {
+		if(!empty($_SESSION['loggedIn']) && $_SESSION['loggedIn']) {
+			$view = new View();
+			$headerView = new View();
+			$footerView = new View();
+
+			switch($this->template) {
+				case 'admin':
+					if($GLOBALS['user']->getAdmin() == 0) {
+						echo "neeeee";
+						exit(1);
+					}
+					$view->setTemplate('admin');
+					$view->assign('sources', $GLOBALS['db']->getAllAssoc("sources"));
+					$view->assign('series', Model::getSeries());
+					break;
+
+				case 'scrape':
+					$view->setTemplate('scrape');
+					$view->assign('scraper', $this->scraper);
+					break;
+
+				case 'seasons':
+					$view->setTemplate('seasons');
+					$view->assign('seasons', Model::getSeasonsBySeriesID($this->request['seriesID']));
+					break;
+
+				case 'season':
+					$view->setTemplate('season');
+					$view->assign('episodes', Model::getEpisodesBySeasonID($this->request['seasonID']));
+					break;
+
+				case 'episode':
+					$view->setTemplate('episode');
+					$view->assign('episode', Model::getEpisodeByID($this->request['episodeID']));
+					$view->assign('videoFile', Model::getAbsouluteVideoPathByEpisodeID($this->request['episodeID']));
+					break;
+
+				case 'movie':
+					$view->setTemplate('movie');
+					$view->assign('movie', Model::getMovieByID($this->request['movieID']));
+					$view->assign('videoFile', Model::getAbsouluteVideoPathByMovieID($this->request['movieID']));
+					break;
+
+				case 'user':
+					$view->setTemplate('user');
+					break;
+				case 'default':
+				default:
+					$view->setTemplate('content');
+					$view->assign('series', Model::getSeries());
+					$view->assign('movies', Model::getMovies());
+			}
+			$this->view->setTemplate('matrix');
+			$headerView->setTemplate('header');
+			$footerView->setTemplate('footer');
+
+			$this->view->assign('header', $headerView->loadTemplate());
+			$this->view->assign('footer', $footerView->loadTemplate());
+			$this->view->assign('content', $view->loadTemplate());
+			return $this->view->loadTemplate();
+		} else {
+			$view = new View();
+			$view->setTemplate('login');
+			return $view->loadTemplate();
+		}
+	}
+}
+?>

+ 187 - 0
inc/database.php

@@ -0,0 +1,187 @@
+<?php
+
+class Database {
+  private $handle = null;
+
+  public function __construct($config) {
+    $this->handle = new mysqli($config['dbHost'], $config['dbUser'], $config['dbPass'], $config['dbName']);
+    $this->handle->set_charset("utf8");
+
+    if($this->handle->connect_error) {
+      echo "DB failed.";
+      trigger_error('Database connection failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    }
+  }
+
+  public function getString($what, $from, $where, $like) {
+    $query = "SELECT `" . $what . "` FROM `" . $from . "` WHERE `" . $where . "` LIKE '" . $like . "';";
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    } else if($res->num_rows > 1 || $res->num_rows < 1) {
+      echo "This shouldn't happen..1";
+      exit(1);
+    }
+
+    return $res->fetch_row()[0];
+  }
+
+  public function getAllAssoc($from, $where = null, $like = null) {
+    if($where && $like) {
+      $query = "SELECT * FROM `" . $from . "` WHERE `" . $where . "` LIKE '" . $like . "';";
+    } else {
+      $query = "SELECT * FROM `" . $from . "`;";
+    }
+
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    } else if($res->num_rows < 1) {
+      echo "This shouldn't happen..2";
+      exit(1);
+    }
+
+    return $res->fetch_all(MYSQLI_ASSOC);
+  }
+
+  public function getAllAssocCustom($from, $custom, $where = null, $like = null) {
+    if($where && $like) {
+      $query = "SELECT * FROM `" . $from . "` WHERE `" . $where . "` LIKE '" . $like . "' " . $custom . ";";
+    } else {
+      $query = "SELECT * FROM `" . $from . "` " . $custom . ";";
+    }
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    } else if($res->num_rows < 1) {
+      echo "This shouldn't happen..3";
+      exit(1);
+    }
+
+    return $res->fetch_all(MYSQLI_ASSOC);
+  }
+
+  public function getAllRow($from, $where, $like) {
+    $query = "SELECT * FROM `" . $from . "` WHERE `" . $where . "` LIKE '" . $like . "';";
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    } else if($res->num_rows < 1) {
+      echo "This shouldn't happen..4";
+      exit(1);
+    }
+
+    return $res->fetch_all(MYSQLI_NUM);
+  }
+
+  public function getAllRowCustom($from, $custom) {
+    $query = "SELECT * FROM `" . $from . "` " . $custom . ";";
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    } else if($res->num_rows < 1) {
+      echo "This shouldn't happen..5";
+      exit(1);
+    }
+
+    return $res->fetch_all(MYSQLI_NUM);
+  }
+
+  public function countRows($from, $where, $like) {
+    $query = "SELECT * FROM `" . $from . "` WHERE `" . $where . "` LIKE '" . $like . "';";
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    } else {
+      return $res->num_rows;
+    }
+  }
+
+  /*
+
+  $cols = array(
+    "moviedb-id",
+    "name",
+    "path",
+    "poster",
+    "backdrop",
+    "overview"
+  );
+
+  $vals = array(
+    "31295",
+    "Misfits",
+    "Misfits",
+    "hia44dQ66CIfYPlLyaVcHRA9DtG.jpg",
+    "m26kKegbYKyLvQZlctSh56j9KlO.jpg",
+    "Misfits is a British science fiction comedy-drama television show, on [...]"
+  );
+
+  insertRow("series", $cols, $vals);
+
+  */
+
+  public function insertRow($into, $cols, $vals) {
+    foreach($vals as $key => $val) {
+      $vals[$key] = $this->handle->real_escape_string($val);
+    }
+    $colString = "(`" . implode('`, `', $cols) . "`)";
+    $valString = "('" . implode("', '", $vals) . "')";
+
+    $query = "INSERT INTO `" . $into . "` " . $colString . " VALUES " . $valString . ";";
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->error, E_USER_ERROR);
+      exit(1);
+    } else {
+      return $this->handle->insert_id;
+    }
+  }
+
+  public function deleteRows($from, $where, $like) {
+    $query = "DELETE FROM `" . $from . "` WHERE `" . $where . "` LIKE '" . $like . "';";
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    } else {
+      return true;
+    }
+  }
+
+  public function updateRow($update, $col, $val, $where, $like) {
+    $query = "UPDATE `" . $update . "` SET `" . $col . "` = " . $val . " WHERE `" . $where . "` LIKE '" . $like . "';";
+
+    $res = $this->handle->query($query);
+    if($res === false) {
+      echo "DB failed.";
+      trigger_error('Database failed: '  . $this->handle->connect_error, E_USER_ERROR);
+      exit(1);
+    } else {
+      return true;
+    }
+  }
+}

+ 65 - 0
inc/functions.php

@@ -0,0 +1,65 @@
+<?php
+
+function pa($debug, $var = false) {
+    echo "<pre>";
+    if($var) {
+        var_dump($debug);
+    } else {
+        print_r($debug);
+    }
+    echo "</pre>";
+}
+
+function curl_download($Url){
+
+    // is cURL installed yet?
+    if (!function_exists('curl_init')){
+        die('Sorry cURL is not installed!');
+    }
+
+    // OK cool - then let's create a new cURL resource handle
+    $ch = curl_init();
+
+    // Now set some options (most are optional)
+
+    // Set URL to download
+    curl_setopt($ch, CURLOPT_URL, $Url);
+
+    // Set a referer
+    //curl_setopt($ch, CURLOPT_REFERER, "http://www.example.org/yay.htm");
+
+    // User agent
+    curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36");
+
+    // Include header in result? (0 = yes, 1 = no)
+    curl_setopt($ch, CURLOPT_HEADER, 0);
+
+    // Should cURL return or print out the data? (true = return, false = print)
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
+    // Timeout in seconds
+    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+
+    // Download the given URL, and return output
+    $output = curl_exec($ch);
+
+    // Close the cURL resource, and free system resources
+    curl_close($ch);
+
+    return $output;
+}
+
+function generatePassword($length = 8) {
+    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+    $count = mb_strlen($chars);
+
+    for ($i = 0, $result = ''; $i < $length; $i++) {
+        $index = rand(0, $count - 1);
+        $result .= mb_substr($chars, $index, 1);
+    }
+
+    return $result;
+}
+
+
+?>

+ 67 - 0
inc/model.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * Klasse für den Datenzugriff
+ */
+class Model {
+
+	public static function getSourceBySourceID($sourceID) {
+		return $GLOBALS['db']->getAllAssocCustom("sources", "ORDER BY `name` ASC", "id", $sourceID);
+	}
+
+	public static function getSeries() {
+		return $GLOBALS['db']->getAllAssocCustom("series", "ORDER BY `name` ASC");
+	}
+
+	public static function getSeriesBySeriesID($seriesID) {
+		return $GLOBALS['db']->getAllAssocCustom("series", "ORDER BY `name` ASC", "id", $seriesID);
+	}
+
+	public static function getMovies() {
+		return $GLOBALS['db']->getAllAssocCustom("movies", "ORDER BY `name` ASC");
+	}
+
+	public static function getSeasonBySeasonID($seasonID) {
+		return $GLOBALS['db']->getAllAssocCustom("series-seasons", "ORDER BY `number` ASC", "id", $seasonID);
+	}
+
+	public static function getSeasonsBySeriesID($seriesID) {
+		return $GLOBALS['db']->getAllAssocCustom("series-seasons", "ORDER BY `number` ASC", "series-id", $seriesID);
+	}
+
+	public static function getEpisodesBySeasonID($seasonID) {
+		return $GLOBALS['db']->getAllAssocCustom("series-episodes", "ORDER BY `number` ASC", "season-id", $seasonID);
+	}
+
+	public static function getEpisodeByID($episodeID) {
+		return $GLOBALS['db']->getAllAssoc("series-episodes", "id", $episodeID);
+	}
+
+	public static function getAbsouluteVideoPathByEpisodeID($episodeID) {
+		$episode = self::getEpisodeByID($episodeID);
+		$season = self::getSeasonBySeasonID($episode[0]['season-id']);
+		$series = self::getSeriesBySeriesID($season[0]['series-id']);
+		$source = self::getSourceBySourceID($series[0]['source']);
+
+		if($season[0]['number'] < 10) {
+			return $source[0]['path'] . "/" . $series[0]['path'] . "/S0" . $season[0]['number'] . "/" . $episode[0]['path'];
+		} else {
+			return $source[0]['path'] . "/" . $series[0]['path'] . "/S" . $season[0]['number'] . "/" . $episode[0]['path'];
+		}
+	}
+
+	public static function getUserIDByMail($userMail) {
+		return $GLOBALS['db']->getAllAssoc("users", "mail", $userMail);
+	}
+
+	public static function getMovieByID($movieID) {
+		return $GLOBALS['db']->getAllAssoc("movies", "id", $movieID);
+	}
+
+	public static function getAbsouluteVideoPathByMovieID($movieID) {
+		$movie = self::getMovieByID($movieID);
+		$source = self::getSourceBySourceID($movie[0]['source']);
+
+		return $source[0]['path'] . "/" . $movie[0]['path'];
+	}
+}
+?>

+ 147 - 0
inc/movieScraper.php

@@ -0,0 +1,147 @@
+<?php
+
+ignore_user_abort(true);
+
+class MovieScraper {
+  private $apiURL = "http://api.themoviedb.org/3/search/movie?api_key=a39779a38e0619f8ae58b09f64522597&query=";
+  private $bannerURL = "https://image.tmdb.org/t/p/original";
+
+  private $outputText = "";
+
+  public function __construct() {
+
+  }
+
+  public function getOutputText() {
+    return $this->outputText;
+  }
+
+  public static function getMovieNameFromFilename($filename) {
+    preg_match("/([a-zA-Z0-9äöüÄÖÜ.]+).([0-9][0-9][0-9][0-9]).([a-z0-9]+)/", $filename, $output);
+    return str_replace(".", " ", $output[1]);
+  }
+
+  public static function sortByPopularity($movieA, $movieB) {    
+    if($movieA['popularity'] == $movieB['popularity']) {
+      return 0;
+    }
+
+    return ($movieA['popularity'] < $movieB['popularity']) ? 1 : -1;
+  }
+
+  public function scrapeFolder($sourceID) {
+    if(!file_exists("img/posters")) {
+        mkdir("img/posters");
+    }
+
+    $source = $GLOBALS['db']->getAllAssoc("sources", "id", $sourceID);
+
+    $fileList = scandir($source[0]['path']);
+    $fileList = array_diff($fileList, array('.', '..', 'formatting.txt', '.Trash-1000'));
+
+    foreach($fileList as $file) {
+      $this->outputText .= "<b>" . self::getMovieNameFromFilename($file) . "</b><br>" . PHP_EOL;
+
+      if($GLOBALS['db']->countRows("movies", "path", $file) > 0) {
+        $this->outputText .= "Exists, skipping..<br><br>" . PHP_EOL;
+        continue;
+      }
+
+      $movie = json_decode(curl_download($this->apiURL . urlencode(self::getMovieNameFromFilename($file)) . "&language=de&include_image_language=de"), true);
+
+      if($movie['total_results'] == 1) {
+        $this->outputText .= "Found 1 movie, downloading...<br>" . PHP_EOL;
+
+        $this->downloadMovieByID($movie['results'][0]['id'], $file, $sourceID);
+      } else if($movie['total_results'] < 1) {
+        $this->outputText .= "<span style=\"color: red;\">Not found!!</span><br>" . PHP_EOL;
+      } else { // multiple search results
+        usort($movie['results'], array("MovieScraper", "sortByPopularity")); // sort results by popularity, so that you don't have to scroll like 500000x
+        foreach($movie['results'] as $result) {
+          $this->outputText .= "<pre>" . print_r($result, true) . "</pre>" . PHP_EOL;
+          $this->outputText .= "<a href=\"?view=scrape&action=scrapeSingleMovie&moviedbID=" . $result['id'] . "&path=" . urlencode($file) . "&sourceID=" . $sourceID . "\" target=\"_blank\">Load</a><br>" . PHP_EOL;
+        }
+      }
+
+      $this->outputText .= "<br>" . PHP_EOL;
+
+      /*
+
+      Array
+        (
+            [page] => 1
+            [results] => Array
+                (
+                    [0] => Array
+                        (
+                            [poster_path] => /hDlezfMSw8oXIFNZ7B9fFLrV8kd.jpg
+                            [popularity] => 1.006821
+                            [id] => 15826
+                            [backdrop_path] => /meQN6iuOulLwOV8LNO6S9z3bJBY.jpg
+                            [vote_average] => 7
+                            [overview] => 1000 Ways to Die is an anthology television series that [...]
+                            [first_air_date] => 2009-02-04
+                            [origin_country] => Array
+                                (
+                                    [0] => US
+                                )
+
+                            [genre_ids] => Array
+                                (
+                                    [0] => 99
+                                )
+
+                            [original_language] => en
+                            [vote_count] => 1
+                            [name] => 1000 Ways to Die
+                            [original_name] => 1000 Ways to Die
+                        )
+
+                )
+
+            [total_results] => 1
+            [total_pages] => 1
+        )
+
+        */
+    }
+
+  }
+
+  public function downloadMovieByID($movieID, $path, $sourceID) {
+
+    $movie = json_decode(curl_download("http://api.themoviedb.org/3/movie/" . $movieID . "?api_key=a39779a38e0619f8ae58b09f64522597&language=de&include_image_language=de"), true);
+
+    $cols = array(
+      "moviedb-id",
+      "name",
+      "path",
+      "poster",
+      "backdrop",
+      "overview",
+      "source"
+    );
+
+    $vals = array(
+      $movie['id'],
+      $movie['title'],
+      $path,
+      $movie['poster_path'],
+      $movie['backdrop_path'],
+      $movie['overview'],
+      $sourceID
+    );
+
+    // Download poster, backdrop
+    if(!file_exists("img/posters" . $movie['poster_path'])) {
+      file_put_contents("img/posters" . $movie['poster_path'], fopen($this->bannerURL . $movie['poster_path'], 'r'));
+    }
+    if(!file_exists("img/posters" . $movie['backdrop_path'])) {
+      file_put_contents("img/posters" . $movie['backdrop_path'], fopen($this->bannerURL . $movie['backdrop_path'], 'r'));
+    }
+
+    $GLOBALS['db']->insertRow("movies", $cols, $vals);
+
+    $this->outputText .= "Done.";
+  }
+}

+ 228 - 0
inc/seriesScraper.php

@@ -0,0 +1,228 @@
+<?php
+
+ignore_user_abort(true);
+
+class SeriesScraper {
+  private $apiURL = "http://api.themoviedb.org/3/search/tv?api_key=a39779a38e0619f8ae58b09f64522597&query=";
+  private $bannerURL = "https://image.tmdb.org/t/p/original";
+
+  private $outputText = "";
+
+  public function __construct() {
+
+  }
+
+
+  public function getOutputText() {
+    return $this->outputText;
+  }
+
+  public static function getEpisodeNumberByFilename($filename) {
+    preg_match("/([a-zA-Z0-9äöüÄÖÜß\-\.\,\w]+)-[S|s]([0-9]+)[E|e]([0-9]+)-([a-zA-Z0-9äöüÄÖÜß\-\.\,\w\:]+)\.([a-zA-Z0-9\(\).*\w\,]+)/", $filename, $output);
+
+    return ltrim($output[3], 0);
+  }
+
+  public static function remove($seriesID) {
+    $series = Model::getSeriesBySeriesID($seriesID);
+    $seasons = Model::getSeasonsBySeriesID($seriesID);
+    foreach($seasons as $season) {
+      $GLOBALS['db']->deleteRows("series-episodes", "season-id", $season['id']);
+    }
+    $GLOBALS['db']->deleteRows("series-seasons", "series-id", $seriesID);
+    $GLOBALS['db']->deleteRows("series", "id", $seriesID);
+
+    // TODO: Remove files
+  }
+
+  public function update($seriesID) {
+    $series = Model::getSeriesBySeriesID($seriesID);
+
+    self::remove($seriesID);
+
+    $this->downloadSeriesByID($series[0]['moviedb-id'], $series[0]['path'], $series[0]['source']);
+  }
+
+  public function scrapeFolder($sourceID) {
+    if(!file_exists("img/posters")) {
+        mkdir("img/posters");
+    }
+
+    $source = $GLOBALS['db']->getAllAssoc("sources", "id", $sourceID);
+
+    $dirList = scandir($source[0]['path']);
+    $dirList = array_diff($dirList, array('.', '..', 'formatting.txt', '.Trash-1000'));
+
+    foreach($dirList as $dir) {
+      $this->outputText .= "<b>" . $dir . "</b><br>" . PHP_EOL;
+
+      if($GLOBALS['db']->countRows("series", "path", $dir) > 0) {
+        $this->outputText .= "Exists, skipping..<br><br>" . PHP_EOL;
+        continue;
+      }
+
+      $series = json_decode(curl_download($this->apiURL . urlencode($dir) . "&language=de&include_image_language=de"), true);
+
+      if($series['total_results'] == 1) {
+        $this->outputText .= "Found 1 series, downloading...<br>" . PHP_EOL;
+
+        $this->downloadSeriesByID($series['results'][0]['id'], $dir, $sourceID);
+      } else if($series['total_results'] < 1) {
+        $this->outputText .= "<span style=\"color: red;\">Not found!!</span><br>" . PHP_EOL;
+      } else { // multiple search results
+        usort($series['results'], array("MovieScraper", "sortByPopularity")); // sort results by popularity, so that you don't have to scroll like 500000x
+        foreach($series['results'] as $result) {
+          $this->outputText .= "<pre>" . print_r($result, true) . "</pre>" . PHP_EOL;
+          $this->outputText .= "<a href=\"?view=scrape&action=scrapeSingleTV&moviedbID=" . $result['id'] . "&path=" . urlencode($dir) . "&sourceID=" . $sourceID . "\" target=\"_blank\">Load</a><br>" . PHP_EOL; // TODO: this
+        }
+      }
+
+      $this->outputText .= "<br>" . PHP_EOL;
+
+      /*
+
+      Array
+        (
+            [page] => 1
+            [results] => Array
+                (
+                    [0] => Array
+                        (
+                            [poster_path] => /hDlezfMSw8oXIFNZ7B9fFLrV8kd.jpg
+                            [popularity] => 1.006821
+                            [id] => 15826
+                            [backdrop_path] => /meQN6iuOulLwOV8LNO6S9z3bJBY.jpg
+                            [vote_average] => 7
+                            [overview] => 1000 Ways to Die is an anthology television series that [...]
+                            [first_air_date] => 2009-02-04
+                            [origin_country] => Array
+                                (
+                                    [0] => US
+                                )
+
+                            [genre_ids] => Array
+                                (
+                                    [0] => 99
+                                )
+
+                            [original_language] => en
+                            [vote_count] => 1
+                            [name] => 1000 Ways to Die
+                            [original_name] => 1000 Ways to Die
+                        )
+
+                )
+
+            [total_results] => 1
+            [total_pages] => 1
+        )
+
+        */
+    }
+
+  }
+
+  public function downloadSeriesByID($seriesID, $path, $sourceID) {
+
+    $source = $GLOBALS['db']->getAllAssoc("sources", "id", $sourceID);
+
+    $series = json_decode(curl_download("http://api.themoviedb.org/3/tv/" . $seriesID . "?api_key=a39779a38e0619f8ae58b09f64522597&language=de&include_image_language=de"), true);
+
+    //$this->outputText .= "<pre>" . print_r($series, true) . "</pre>";
+
+    // Download poster, backdrop
+    if(!file_exists("img/posters" . $series['poster_path'])) {
+      file_put_contents("img/posters" . $series['poster_path'], fopen($this->bannerURL . $series['poster_path'], 'r'));
+    }
+
+    if(!file_exists("img/posters" . $series['backdrop_path'])) {
+      file_put_contents("img/posters" . $series['backdrop_path'], fopen($this->bannerURL . $series['backdrop_path'], 'r'));
+    }
+
+    $cols = array(
+      "moviedb-id",
+      "name",
+      "path",
+      "poster",
+      "backdrop",
+      "overview",
+      "source"
+    );
+
+    $vals = array(
+      $series['id'],
+      $series['name'],
+      $path,
+      $series['poster_path'],
+      $series['backdrop_path'],
+      $series['overview'],
+      $sourceID
+    );
+
+    $dbSeriesID = $GLOBALS['db']->insertRow("series", $cols, $vals);
+
+    $localSeasons = scandir($source[0]['path'] . "/" . $path);
+    $localSeasons = array_diff($localSeasons, array('.', '..', 'formatting.txt', '.Trash-1000'));
+
+    foreach($localSeasons as $localSeason) {
+      $season = array_search(ltrim(ltrim($localSeason, "S"), 0), array_column($series['seasons'], 'season_number'));
+
+      $cols = array(
+        "series-id",
+        "number",
+        "poster"
+      );
+
+      $vals = array(
+        $dbSeriesID,
+        $series['seasons'][$season]['season_number'],
+        $series['seasons'][$season]['poster_path']
+      );
+
+      $dbSeasonID = $GLOBALS['db'] -> insertRow("series-seasons", $cols, $vals);
+
+      if(!file_exists("img/posters" . $series['seasons'][$season]['poster_path'])) {
+        file_put_contents("img/posters" . $series['seasons'][$season]['poster_path'], fopen($this->bannerURL . $series['seasons'][$season]['poster_path'], 'r'));
+      }
+
+      $localEpisodes = scandir($source[0]['path'] . "/" . $path . "/" . $localSeason);
+      $localEpisodes = array_diff($localEpisodes, array('.', '..', 'formatting.txt', '.Trash-1000'));
+
+      $dbEpisodes = get_object_vars(json_decode(curl_download("http://api.themoviedb.org/3/tv/" . $seriesID . "/season/" . ltrim(ltrim($localSeason, "S"), 0) . "?api_key=a39779a38e0619f8ae58b09f64522597&language=de&include_image_language=de")));
+
+      $episodes = array();
+
+      foreach($dbEpisodes['episodes'] as $episode) {
+        $episodes[] = get_object_vars($episode);
+      }
+
+      foreach($localEpisodes as $localEpisode) {
+        $episode = array_search($this::getEpisodeNumberByFilename($localEpisode), array_column($episodes, 'episode_number'));
+
+        $cols = array(
+          "season-id",
+          "number",
+          "name",
+          "thumb",
+          "path"
+        );
+
+        $vals = array(
+          $dbSeasonID,
+          $episodes[$episode]['episode_number'],
+          $episodes[$episode]['name'],
+          $episodes[$episode]['still_path'],
+          $localEpisode
+        );
+
+        $GLOBALS['db'] -> insertRow("series-episodes", $cols, $vals);
+
+        if(!file_exists("img/posters" . $episodes[$episode]['still_path'])) {
+          file_put_contents("img/posters" . $episodes[$episode]['still_path'], fopen($this->bannerURL . $episodes[$episode]['still_path'], 'r'));
+        }
+      }
+    }
+
+    $this->outputText .= "Done.";
+  }
+}

+ 500 - 0
inc/updateMetadata.php

@@ -0,0 +1,500 @@
+<?php
+/*
+ignore_user_abort(true);
+require('functions.php');
+
+// Serien
+
+$apiURL = "http://thetvdb.com/api/084F3E73D176AD88/";
+$bannerURL = "http://www.thetvdb.com/banners/";
+
+if(!file_exists("posters")) {
+    mkdir("posters");
+}
+
+// http://www.thetvdb.com/wiki/index.php/Programmers_API
+
+// Step 2 (dev)
+
+$languages = curl_download($apiURL . "languages.xml");
+$languages = new SimpleXMLElement($languages);
+
+$german = $languages->xpath('/Languages/Language/abbreviation[.="de"]/parent::*');
+$english = $languages->xpath('/Languages/Language/abbreviation[.="en"]/parent::*');
+
+// Step 1
+
+$mirrors = curl_download($apiURL . "mirrors.xml");
+$mirrors = new SimpleXMLElement($mirrors);
+
+// xml mirrors
+
+$xmlMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="1"]/parent::*');
+
+if(sizeof($xmlMirrors < 1)) {
+    $xmlMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="3"]/parent::*');
+
+    if(sizeof($xmlMirrors < 1)) {
+        $xmlMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="5"]/parent::*');
+
+        if(sizeof($xmlMirrors < 1)) {
+            $xmlMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="7"]/parent::*');
+        }
+    }
+}
+
+// banner mirrors
+
+$bannerMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="2"]/parent::*');
+
+if(sizeof($bannerMirrors < 1)) {
+    $bannerMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="3"]/parent::*');
+
+    if(sizeof($bannerMirrors < 1)) {
+        $bannerMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="6"]/parent::*');
+
+        if(sizeof($bannerMirrors < 1)) {
+            $bannerMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="7"]/parent::*');
+        }
+    }
+}
+
+// zip mirrors
+
+$zipMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="4"]/parent::*');
+
+if(sizeof($zipMirrors < 1)) {
+    $zipMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="5"]/parent::*');
+
+    if(sizeof($zipMirrors < 1)) {
+        $zipMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="6"]/parent::*');
+
+        if(sizeof($zipMirrors < 1)) {
+            $zipMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="7"]/parent::*');
+        }
+    }
+}
+
+// Step 2
+
+$serverTime = curl_download("http://thetvdb.com/api/Updates.php?type=none");
+$serverTime = new SimpleXMLElement($serverTime);
+
+// Step 3
+
+if(isset($_REQUEST['action']) && $_REQUEST['action'] == "singleDownload") {
+    $seriesName = $_REQUEST['seriesname'];
+    $seriesID = $_REQUEST['seriesid'];
+
+    $series = curl_download($apiURL . "series/" . $seriesID . "/" . (string) $german[0]->abbreviation . ".xml");
+
+    if(strpos($series, 'Not Found') !== false) {
+        $series = curl_download($apiURL . "series/" . $seriesID . "/" . (string) $english[0]->abbreviation . ".xml");
+    }
+
+    $banners = curl_download($apiURL . "series/" . $seriesID . "/banners.xml");
+    $banners = new SimpleXMLElement($banners);
+
+    $poster = $banners->xpath("/Banners/Banner/BannerType[text()='poster']/../Language[text()='" . $german[0]->abbreviation . "']/parent::*");
+
+    if(sizeof($poster) < 1) {
+        $poster = $banners->xpath("/Banners/Banner/BannerType[text()='poster']/../Language[text()='" . $english[0]->abbreviation . "']/parent::*");
+    }
+
+    $poster = $poster[0]->BannerPath;
+    $poster = $bannerURL . $poster;
+
+    $seasons = $banners->xpath("/Banners/Banner/BannerType[text()='season']/../BannerType2[text()='season']/../Language[text()='" . $german[0]->abbreviation . "']/parent::*");
+
+    if(sizeof($seasons) > 0) {
+        foreach($seasons as $season) {
+            $seasonURL = $bannerURL . $season->BannerPath;
+            file_put_contents("posters/" . $seriesName . "_" . (string) $season->Season[0] . ".jpg", fopen($seasonURL, 'r'));
+        }
+    }
+
+    $seasons = $banners->xpath("/Banners/Banner/BannerType[text()='season']/../BannerType2[text()='season']/../Language[text()='" . $english[0]->abbreviation . "']/parent::*");
+
+    if(sizeof($seasons) > 0) {
+        foreach($seasons as $season) {
+            if(file_exists("posters/" . $seriesName . "_" . (string) $season->Season[0] . ".jpg")) {
+                continue;
+            }
+            $seasonURL = $bannerURL . $season->BannerPath;
+            file_put_contents("posters/" . $seriesName . "_" . (string) $season->Season[0] . ".jpg", fopen($seasonURL, 'r'));
+        }
+    }
+
+    $episodes = curl_download("http://thetvdb.com/api/084F3E73D176AD88/series/" . $seriesID . "/all/" . $german[0]->abbreviation . ".xml");
+    $episodes = new SimpleXMLElement($episodes);
+
+    if(sizeof($episodes->Episode) > 0) {
+        foreach($episodes->Episode as $episode) {
+
+            if(file_exists("posters/" . $seriesName . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg")) {
+                continue;
+            }
+            $episodeURL = $bannerURL . $episode->filename;
+            file_put_contents("posters/" . $seriesName . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg", fopen($episodeURL, 'r'));
+        }
+    }
+
+    $episodes = curl_download("http://thetvdb.com/api/084F3E73D176AD88/series/" . $seriesID . "/all/" . $english[0]->abbreviation . ".xml");
+    $episodes = new SimpleXMLElement($episodes);
+
+    if(sizeof($episodes->Episode) > 0) {
+        foreach($episodes->Episode as $episode) {
+
+            if(file_exists("posters/" . $seriesName . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg")) {
+                continue;
+            }
+            $episodeURL = $bannerURL . $episode->filename;
+            file_put_contents("posters/" . $seriesName . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg", fopen($episodeURL, 'r'));
+        }
+    }
+
+    $series = new SimpleXMLElement($series);
+
+    file_put_contents("posters/" . $seriesName . ".jpg", fopen($poster, 'r'));
+
+    echo "Done.";
+
+} else if(!isset($_REQUEST['action'])) {
+
+    $list = scandir("/media/Serien");
+    $list = array_diff($list, array('.', '..'));
+
+    foreach($list as $name) {
+
+        $object = str_split($name, sizeof($name));
+        if($object[0] == "." && $object[1] != ".") {
+            continue;
+        }
+
+        echo $name . "<br>";
+
+        if(file_exists("posters/" . $name . ".jpg")) {
+            echo "skipping..<br><br>";
+            continue;
+        }
+
+
+        $series = curl_download("http://thetvdb.com/api/GetSeries.php?seriesname=" . urlencode($name));
+        $series = new SimpleXMLElement($series);
+
+        if(sizeof($series) > 1) {
+
+            foreach($series as $serie) {
+                pa($serie);
+                echo "<a target=\"_blank\" href=\"?action=singleDownload&seriesname=" . (string) $serie->SeriesName . "&seriesid=" . (string) $serie->seriesid . "\">Load</a><br>";
+            }
+        } else {
+            $seriesID = (string) $series->Series->seriesid[0];
+
+            $series = curl_download($apiURL . "series/" . $seriesID . "/" . (string) $german[0]->abbreviation . ".xml");
+
+            if(strpos($series, 'Not Found') !== false) {
+                $series = curl_download($apiURL . "series/" . $seriesID . "/" . (string) $english[0]->abbreviation . ".xml");
+            }
+
+            $banners = curl_download($apiURL . "series/" . $seriesID . "/banners.xml");
+            $banners = new SimpleXMLElement($banners);
+
+            $poster = $banners->xpath("/Banners/Banner/BannerType[text()='poster']/../Language[text()='" . $german[0]->abbreviation . "']/parent::*");
+
+            if(sizeof($poster) < 1) {
+                $poster = $banners->xpath("/Banners/Banner/BannerType[text()='poster']/../Language[text()='" . $english[0]->abbreviation . "']/parent::*");
+            }
+
+            $poster = $poster[0]->BannerPath;
+            $poster = $bannerURL . $poster;
+
+            $seasons = $banners->xpath("/Banners/Banner/BannerType[text()='season']/../BannerType2[text()='season']/../Language[text()='" . $german[0]->abbreviation . "']/parent::*");
+
+            if(sizeof($seasons) > 0) {
+                foreach($seasons as $season) {
+                    $seasonURL = $bannerURL . $season->BannerPath;
+                    file_put_contents("posters/" . $name . "_" . (string) $season->Season[0] . ".jpg", fopen($seasonURL, 'r'));
+                }
+            }
+
+            $seasons = $banners->xpath("/Banners/Banner/BannerType[text()='season']/../BannerType2[text()='season']/../Language[text()='" . $english[0]->abbreviation . "']/parent::*");
+
+            if(sizeof($seasons) > 0) {
+                foreach($seasons as $season) {
+                    if(file_exists("posters/" . $name . "_" . (string) $season->Season[0] . ".jpg")) {
+                        continue;
+                    }
+                    $seasonURL = $bannerURL . $season->BannerPath;
+                    file_put_contents("posters/" . $name . "_" . (string) $season->Season[0] . ".jpg", fopen($seasonURL, 'r'));
+                }
+            }
+
+            $episodes = curl_download("http://thetvdb.com/api/084F3E73D176AD88/series/" . $seriesID . "/all/" . $german[0]->abbreviation . ".xml");
+            $episodes = new SimpleXMLElement($episodes);
+
+            if(sizeof($episodes->Episode) > 0) {
+                foreach($episodes->Episode as $episode) {
+
+                    if(file_exists("posters/" . $name . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg")) {
+                        continue;
+                    }
+                    $episodeURL = $bannerURL . $episode->filename;
+                    file_put_contents("posters/" . $name . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg", fopen($episodeURL, 'r'));
+                }
+            }
+
+            $episodes = curl_download("http://thetvdb.com/api/084F3E73D176AD88/series/" . $seriesID . "/all/" . $english[0]->abbreviation . ".xml");
+            $episodes = new SimpleXMLElement($episodes);
+
+            if(sizeof($episodes->Episode) > 0) {
+                foreach($episodes->Episode as $episode) {
+
+                    if(file_exists("posters/" . $name . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg")) {
+                        continue;
+                    }
+                    $episodeURL = $bannerURL . $episode->filename;
+                    file_put_contents("posters/" . $name . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg", fopen($episodeURL, 'r'));
+                }
+            }
+
+            $series = new SimpleXMLElement($series);
+
+            file_put_contents("posters/" . $name . ".jpg", fopen($poster, 'r'));
+        }
+    }
+}
+
+*/
+// Filme
+
+$movieAPIUrl = "https://api.themoviedb.org/3/search/movie?api_key=a39779a38e0619f8ae58b09f64522597&query=";
+$moviePosterURL = "https://image.tmdb.org/t/p/original/";
+
+
+
+if(isset($_REQUEST['action']) && $_REQUEST['action'] == "singleDownloadMovie") {
+    $movieName = $_REQUEST['moviename'];
+    $movieID = $_REQUEST['movieid'];
+
+    $movie = curl_download("https://api.themoviedb.org/3/movie/" . $movieID . "?api_key=a39779a38e0619f8ae58b09f64522597&language=de&include_image_language=de");
+
+    if(strpos($movie, 'not be found') !== false) {
+        echo "something went wrong";
+    }
+
+    $movie = json_decode($movie);
+    file_put_contents("posters/" . $movieName . ".jpg", fopen($moviePosterURL . $movie->poster_path, 'r'));
+
+    echo "Done.";
+
+} else if(!isset($_REQUEST['action'])) {
+    $list = scandir("/media/Filme");
+    $list = array_diff($list, array('.', '..'));
+
+    foreach($list as $movieName) {
+
+        $movieName = explode('.', $movieName);
+        unset($movieName[sizeof($movieName) - 1]);
+        unset($movieName[sizeof($movieName) - 1]);
+        $movieName = implode(' ', $movieName);
+        $movieName = str_replace(" Directors Cut", "", $movieName);
+
+        pa($movieName);
+
+        if(file_exists("posters/" . $movieName . ".jpg")) {
+            echo "skipping..<br><br>";
+            continue;
+        }
+
+        $movie = json_decode(curl_download($movieAPIUrl . urlencode($movieName)));
+
+        if(sizeof($movie->results) > 1) {
+            foreach($movie->results as $result) {
+                pa($result);
+
+                echo "<a target=\"_blank\" href=\"?action=singleDownloadMovie&moviename=" . $movieName . "&movieid=" . (string) $result->id . "\">Load</a><br>";
+            }
+        } else {
+            $movie = json_decode(curl_download("https://api.themoviedb.org/3/movie/" . (string) $movie->results[0]->id . "?api_key=a39779a38e0619f8ae58b09f64522597&language=de&include_image_language=de"));
+
+            $poster = $moviePosterURL . $movie->poster_path;
+            file_put_contents("posters/" . $movieName . ".jpg", fopen($poster, 'r'));
+        }
+    }
+}
+/*
+?>
+
+
+
+$languages = curl_download($this->apiURL . "languages.xml");
+$languages = new SimpleXMLElement($languages);
+
+$german = $languages->xpath('/Languages/Language/abbreviation[.="de"]/parent::*');
+$english = $languages->xpath('/Languages/Language/abbreviation[.="en"]/parent::*');
+
+$mirrors = curl_download($this->apiURL . "mirrors.xml");
+$mirrors = new SimpleXMLElement($mirrors);
+
+$xmlMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="1"]/parent::*');
+
+if(sizeof($xmlMirrors < 1)) {
+    $xmlMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="3"]/parent::*');
+
+    if(sizeof($xmlMirrors < 1)) {
+        $xmlMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="5"]/parent::*');
+
+        if(sizeof($xmlMirrors < 1)) {
+            $xmlMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="7"]/parent::*');
+        }
+    }
+}
+
+$bannerMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="2"]/parent::*');
+
+if(sizeof($bannerMirrors < 1)) {
+    $bannerMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="3"]/parent::*');
+
+    if(sizeof($bannerMirrors < 1)) {
+        $bannerMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="6"]/parent::*');
+
+        if(sizeof($bannerMirrors < 1)) {
+            $bannerMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="7"]/parent::*');
+        }
+    }
+}
+
+$zipMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="4"]/parent::*');
+
+if(sizeof($zipMirrors < 1)) {
+    $zipMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="5"]/parent::*');
+
+    if(sizeof($zipMirrors < 1)) {
+        $zipMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="6"]/parent::*');
+
+        if(sizeof($zipMirrors < 1)) {
+            $zipMirrors = $mirrors->xpath('/Mirrors/Mirror/typemask[.="7"]/parent::*');
+        }
+    }
+}
+
+$serverTime = curl_download("http://thetvdb.com/api/Updates.php?type=none");
+$serverTime = new SimpleXMLElement($serverTime);
+
+$list = scandir($path);
+$list = array_diff($list, array('.', '..', 'formatting.txt'));
+
+foreach($list as $name) {
+
+  $object = str_split($name, sizeof($name));
+  if($object[0] == "." && $object[1] != ".") { // Ignore dotfiles
+      continue;
+  }
+
+  error_log($name);
+
+  $this->outputText .= $name . "<br>" . PHP_EOL;
+
+  if(file_exists("img/posters/" . $name . ".jpg")) {
+      $this->outputText .= "skipping..<br><br>" . PHP_EOL;
+      continue;
+  }
+
+  $series = curl_download("http://thetvdb.com/api/GetSeries.php?seriesname=" . urlencode($name));
+  $series = new SimpleXMLElement($series);
+
+  if(sizeof($series) > 1) {
+    foreach($series as $serie) {
+      $this->outputText .= "<pre>" . print_r($serie, true) . "</pre>" . PHP_EOL;
+      $this->outputText .= "<a target=\"_blank\" href=\"?action=singleDownload&seriesname=" . (string) $serie->SeriesName . "&seriesid=" . (string) $serie->seriesid . "\">Load</a><br>" . PHP_EOL;
+    }
+  } else {
+    error_log($seriesID);
+    $seriesID = (string) $series->Series->seriesid[0];
+
+    $series = curl_download($this->apiURL . "series/" . $seriesID . "/" . (string) $german[0]->abbreviation . ".xml");
+
+    if(strpos($series, 'Not Found') !== false) {
+        $series = curl_download($this->apiURL . "series/" . $seriesID . "/" . (string) $english[0]->abbreviation . ".xml");
+    }
+
+
+    $banners = curl_download($this->apiURL . "series/" . $seriesID . "/banners.xml");
+    $banners = new SimpleXMLElement($banners);
+
+    $poster = $banners->xpath("/Banners/Banner/BannerType[text()='poster']/../Language[text()='" . $german[0]->abbreviation . "']/parent::*");
+
+    if(sizeof($poster) < 1) {
+        $poster = $banners->xpath("/Banners/Banner/BannerType[text()='poster']/../Language[text()='" . $english[0]->abbreviation . "']/parent::*");
+    }
+
+    $poster = $poster[0]->BannerPath;
+    $poster = $this->bannerURL . $poster;
+
+    file_put_contents("img/posters/" . $name . ".jpg", fopen($poster, 'r'));
+
+    $seasons = $banners->xpath("/Banners/Banner/BannerType[text()='season']/../BannerType2[text()='season']/../Language[text()='" . $german[0]->abbreviation . "']/parent::*");
+
+
+
+    if(sizeof($seasons) > 0) {
+        foreach($seasons as $season) {
+            $seasonURL = $this->bannerURL . $season->BannerPath;
+            file_put_contents("img/posters/" . $name . "_" . (string) $season->Season[0] . ".jpg", fopen($seasonURL, 'r'));
+        }
+    }
+
+    $seasons = $banners->xpath("/Banners/Banner/BannerType[text()='season']/../BannerType2[text()='season']/../Language[text()='" . $english[0]->abbreviation . "']/parent::*");
+
+    if(sizeof($seasons) > 0) {
+        foreach($seasons as $season) {
+            if(file_exists("img/posters/" . $name . "_" . (string) $season->Season[0] . ".jpg")) {
+                continue;
+            }
+            $seasonURL = $this->bannerURL . $season->BannerPath;
+            file_put_contents("img/posters/" . $name . "_" . (string) $season->Season[0] . ".jpg", fopen($seasonURL, 'r'));
+        }
+    }
+
+    $episodes = curl_download("http://thetvdb.com/api/084F3E73D176AD88/series/" . $seriesID . "/all/" . $german[0]->abbreviation . ".xml");
+    $episodes = new SimpleXMLElement($episodes);
+
+    if(sizeof($episodes->Episode) > 0) {
+        foreach($episodes->Episode as $episode) {
+
+            if(file_exists("img/posters/" . $name . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg")) {
+                continue;
+            }
+
+            if(!empty($episode->filename)) {
+              $episodeURL = $this->bannerURL . $episode->filename;
+              file_put_contents("img/posters/" . $name . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg", fopen($episodeURL, 'r'));
+            }
+        }
+    }
+
+    $episodes = curl_download("http://thetvdb.com/api/084F3E73D176AD88/series/" . $seriesID . "/all/" . $english[0]->abbreviation . ".xml");
+    $episodes = new SimpleXMLElement($episodes);
+
+    if(sizeof($episodes->Episode) > 0) {
+        foreach($episodes->Episode as $episode) {
+
+            if(file_exists("img/posters/" . $name . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg")) {
+                continue;
+            }
+
+            if(!empty($episode->filename)) {
+              $episodeURL = $this->bannerURL . $episode->filename;
+              file_put_contents("img/posters/" . $name . "_" . (string) $episode->SeasonNumber[0] . "_" . (string) $episode->EpisodeNumber . ".jpg", fopen($episodeURL, 'r'));
+            }
+
+        }
+    }
+
+    error_log("serie done");
+
+  }
+
+  error_log("done");
+}

+ 145 - 0
inc/user.php

@@ -0,0 +1,145 @@
+<?php
+class User {
+  private $id = null;
+  private $mail = null;
+  private $admin = null;
+
+  public function __construct($mail) {
+    $user = $GLOBALS['db']->getAllAssoc("users", "mail", $mail);
+
+    $this->id = $user[0]['id'];
+    $this->mail = $user[0]['mail'];
+    $this->admin = $user[0]['admin'];
+  }
+
+  public static function login($request) {
+    $hashedPass = $GLOBALS['db']->getString("pass", "users", "mail", $request['mail']);
+
+    if($hashedPass == md5($request['pass'])) {
+      $_SESSION['loggedIn'] = true;
+      $_SESSION['mail'] = $request['mail'];
+
+      header("Location: /");
+    } else {
+      echo "PW mismatch, try again.";
+      exit(1);
+    }
+  }
+
+  public static function logout() {
+    session_destroy();
+    header("Location: /");
+  }
+
+  public static function update($newPassword, $newPasswordConfirmation, $newEmail, $oldEmail, $logout = true) {
+    if($newPassword && $newPasswordConfirmation) {
+      if($newPassword == $newPasswordConfirmation) {
+        $GLOBALS['db']->updateRow("users", "pass", "MD5('" . $newPassword . "')", "id", Model::getUserIDByMail($oldEmail)[0]['id']);
+      } else {
+        return "Passwords don't match.";
+      }
+    }
+
+    $GLOBALS['db']->updateRow("users", "mail", "'" . $newEmail . "'", "id", Model::getUserIDByMail($oldEmail)[0]['id']);
+
+    if($logout) {
+      User::logout();
+    }
+  }
+
+  public static function invite($email) {
+    $password = generatePassword();
+    $invite = generatePassword(16);
+
+    $cols = array(
+      "mail",
+      "pass"
+    );
+
+    $vals = array(
+      $email,
+      $password,
+    );
+
+    $GLOBALS['db']->insertRow("users", $cols, $vals);
+    self::update($password, $password, $email, $email, false);
+
+    $msg = 'Was geht,' . PHP_EOL . "Hier deine Accountdaten:" . PHP_EOL . "Email: Diese Email-Adresse" . PHP_EOL . "Passwort: " . $password . PHP_EOL . "PW bitte ändern!";
+
+    mail($email, "Moeflix invite", $msg, 'From: moritz+moeflix@mmnx.de');
+
+  }
+
+  /**
+   * Get the value of Id
+   *
+   *
+   * @return mixed
+   *
+   */
+
+  public function getId() {
+      return $this->id;
+  }
+
+  /**
+   * Set the value of Id
+   *
+   *
+   * @param mixed id
+   *
+   */
+
+  public function setId($id) {
+      $this->id = $id;
+   }
+
+  /**
+   * Get the value of Mail
+   *
+   *
+   * @return mixed
+   *
+   */
+
+  public function getMail() {
+      return $this->mail;
+  }
+
+  /**
+   * Set the value of Mail
+   *
+   *
+   * @param mixed mail
+   *
+   */
+
+  public function setMail($mail) {
+      $this->mail = $mail;
+   }
+
+  /**
+   * Get the value of Admin
+   *
+   *
+   * @return mixed
+   *
+   */
+
+  public function getAdmin() {
+      return $this->admin;
+  }
+
+  /**
+   * Set the value of Admin
+   *
+   *
+   * @param mixed admin
+   *
+   */
+
+  public function setAdmin($admin) {
+      $this->admin = $admin;
+   }
+
+}

+ 61 - 0
inc/view.php

@@ -0,0 +1,61 @@
+<?php
+class View {
+
+	// Pfad zum Template
+	private $path = 'templates';
+	// Name des Templates, in dem Fall das Standardtemplate.
+	private $template = 'default';
+
+	/**
+	 * Enthält die Variablen, die in das Template eingebetet
+	 * werden sollen.
+	 */
+	private $_ = array();
+
+	/**
+	 * Ordnet eine Variable einem bestimmten Schl&uuml;ssel zu.
+	 *
+	 * @param String $key Schlüssel
+	 * @param String $value Variable
+	 */
+	public function assign($key, $value) {
+		$this->_[$key] = $value;
+	}
+
+
+	/**
+	 * Setzt den Namen des Templates.
+	 *
+	 * @param String $template Name des Templates.
+	 */
+	public function setTemplate($template = 'default') {
+		$this->template = $template;
+	}
+
+
+	/**
+	 * Das Template-File laden und zurückgeben
+	 *
+	 * @param string $tpl Der Name des Template-Files (falls es nicht vorher
+	 * 						über steTemplate() zugewiesen wurde).
+	 * @return string Der Output des Templates.
+	 */
+	public function loadTemplate() {
+		$tpl = $this->template;
+		$file = $this->path . DIRECTORY_SEPARATOR . $tpl . '.php';
+
+		if(file_exists($file)) {
+			ob_start();
+
+			include $file;
+
+			$output = ob_get_contents();
+			ob_end_clean();
+
+			return $output;
+		} else {
+			return 'could not find template';
+		}
+	}
+}
+?>

+ 17 - 0
index.php

@@ -0,0 +1,17 @@
+<?php
+session_start();
+include('inc/config.inc.php');
+include('inc/database.php');
+include('inc/controller.php');
+include('inc/model.php');
+include('inc/view.php');
+include('inc/user.php');
+include('inc/seriesScraper.php');
+include('inc/movieScraper.php');
+include('inc/functions.php');
+
+$request = array_merge($_GET, $_POST); // = $_REQUEST - $_COOKIE
+$controller = new Controller($request);
+echo $controller->display();
+
+?>

+ 130 - 0
readfile.php

@@ -0,0 +1,130 @@
+<?php
+/**
+ * Description of VideoStream
+ *
+ * @author Rana
+ * @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
+ */
+class VideoStream
+{
+    private $path = "";
+    private $stream = "";
+    private $buffer = 102400;
+    private $start  = -1;
+    private $end    = -1;
+    private $size   = 0;
+
+    function __construct($filePath)
+    {
+        $this->path = $filePath;
+    }
+
+    /**
+     * Open stream
+     */
+    private function open()
+    {
+        if (!($this->stream = fopen($this->path, 'rb'))) {
+            die('Could not open stream for reading');
+        }
+
+    }
+
+    /**
+     * Set proper header to serve the video content
+     */
+    private function setHeader()
+    {
+        ob_get_clean();
+        header("Content-Type: video/mp4");
+        header("Cache-Control: max-age=2592000, public");
+        header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
+        header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
+        $this->start = 0;
+        $this->size  = filesize($this->path);
+        $this->end   = $this->size - 1;
+        //header("Accept-Ranges: 0-".$this->end);
+        header('Accept-Ranges: bytes');
+
+        if (isset($_SERVER['HTTP_RANGE'])) {
+
+            $c_start = $this->start;
+            $c_end = $this->end;
+
+            list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+            if (strpos($range, ',') !== false) {
+                header('HTTP/1.1 416 Requested Range Not Satisfiable');
+                header("Content-Range: bytes $this->start-$this->end/$this->size");
+                exit;
+            }
+            if ($range == '-') {
+                $c_start = $this->size - substr($range, 1);
+            }else{
+                $range = explode('-', $range);
+                $c_start = $range[0];
+
+                $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
+            }
+            $c_end = ($c_end > $this->end) ? $this->end : $c_end;
+            if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
+                header('HTTP/1.1 416 Requested Range Not Satisfiable');
+                header("Content-Range: bytes $this->start-$this->end/$this->size");
+                exit;
+            }
+            $this->start = $c_start;
+            $this->end = $c_end;
+            $length = $this->end - $this->start + 1;
+            fseek($this->stream, $this->start);
+            header('HTTP/1.1 206 Partial Content');
+            header("Content-Length: ".$length);
+            header("Content-Range: bytes $this->start-$this->end/".$this->size);
+        }
+        else
+        {
+            header("Content-Length: ".$this->size);
+        }
+
+    }
+
+    /**
+     * close curretly opened stream
+     */
+    private function end()
+    {
+        fclose($this->stream);
+        exit;
+    }
+
+    /**
+     * perform the streaming of calculated range
+     */
+    private function stream()
+    {
+        $i = $this->start;
+        set_time_limit(0);
+        while(!feof($this->stream) && $i <= $this->end) {
+            $bytesToRead = $this->buffer;
+            if(($i+$bytesToRead) > $this->end) {
+                $bytesToRead = $this->end - $i + 1;
+            }
+            $data = fread($this->stream, $bytesToRead);
+            echo $data;
+            flush();
+            $i += $bytesToRead;
+        }
+    }
+
+    /**
+     * Start streaming video content
+     */
+    function start()
+    {
+        $this->open();
+        $this->setHeader();
+        $this->stream();
+        $this->end();
+    }
+}
+
+$stream = new VideoStream(base64_decode(urldecode($_GET['file'])));
+$stream->start();

+ 49 - 0
templates/admin.php

@@ -0,0 +1,49 @@
+    <h2>you're in admin sheeeet</h2>
+    <h1>Invite</h1>
+    <form class="form-horizontal" action="?view=admin&action=invite" method="post">
+      <div class="form-group">
+        <label for="inviteMail" class="col-sm-2 control-label">Email</label>
+        <div class="col-sm-6">
+          <input type="email" class="form-control" id="inviteMail" name="inviteMail" placeholder="Email">
+        </div>
+      </div>
+      <div class="form-group">
+        <div class="col-sm-offset-2 col-sm-10">
+          <button type="submit" class="btn btn-default">Invite</button>
+        </div>
+      </div>
+    </form>
+
+    <h1>Sources</h1>
+    <?php
+      foreach($this->_['sources'] as $source) {
+        echo "<a href=\"/?view=scrape&action=scrape&sourceID=" . $source['id'] . "\"><i class=\"fa fa-search\"></i></a> " . $source['name'] . " - " . $source['path'];
+        echo "<br>";
+      }
+      ?>
+      <hr>
+      <h1>Series</h1>
+      <table class="table table-striped">
+        <thead>
+          <tr>
+            <td>#</td>
+            <td>MovieDB-ID</td>
+            <td>Name</td>
+            <td></td>
+            <td></td>
+          </tr>
+        </thead>
+
+      <?php
+
+      foreach($this->_['series'] as $series) {
+        echo "<tr>";
+        echo "<td>" . $series['id'] . "</td>";
+        echo "<td>" . $series['moviedb-id'] . "</td>";
+        echo "<td>" . $series['name'] . "</td>";
+        echo "<td><a href=\"?view=admin&action=updateSeries&seriesID=" . $series['id'] . "\"><i class=\"fa fa-refresh\"></i></a></td>";
+        echo "<td><a href=\"?view=admin&action=removeSeries&seriesID=" . $series['id'] . "\"><i class=\"fa fa-trash\"></i></a></td>";
+      }
+
+      ?>
+    </table>

+ 32 - 0
templates/content.php

@@ -0,0 +1,32 @@
+    <div class="container-fluid">
+      <h1>Serien</h1>
+      <div class="row">
+        <?php foreach($this->_['series'] as $series) { ?>
+
+        <div class="col-sm-3">
+          <div class="image-zoom">
+            <a href="?view=seasons&seriesID=<?php echo $series['id']; ?>">
+              <img class="img-responsive" src="/img/posters/<?php echo $series['poster']; ?>" alt="<?php echo $series['name']; ?>">
+            </a>
+          </div>
+        </div>
+
+        <?php
+        }
+        ?>
+      </div>
+      <h1>Filme</h1>
+      <div class="row">
+        <?php foreach($this->_['movies'] as $movie) { ?>
+
+          <div class="col-sm-3">
+            <div class="image-zoom">
+              <a href="?view=movie&movieID=<?php echo $movie['id']; ?>">
+                <img class="img-responsive" src="/img/posters/<?php echo $movie['poster']; ?>" alt="<?php echo $movie['name']; ?>">
+              </a>
+            </div>
+          </div>
+
+        <?php } ?>
+      </div>
+    </div>

+ 12 - 0
templates/default.php

@@ -0,0 +1,12 @@
+<?php
+
+// TODO: DELETE
+foreach($this->_['entries'] as $entry) {
+?>
+
+  <h2><a href="?view=entry&id=<?php echo $entry['id'] ?>"><?php echo $entry['title']; ?></a></h2>
+  <p><?php echo $entry['content']; ?></p>
+
+<?php
+}
+?>

+ 3 - 0
templates/entry.php

@@ -0,0 +1,3 @@
+<h2><?php echo $this->_['title']; ?></h2>
+<p><?php echo $this->_['content']; ?></p>
+<a href="?view=deafult">Zur&uuml;ck zur &Uuml;bersicht</a>

+ 11 - 0
templates/episode.php

@@ -0,0 +1,11 @@
+<div class="container-fluid">
+  <h1><?php echo $this->_['episode'][0]['number'] . " - " . $this->_['episode'][0]['name']; ?></h1>
+  <div class="row">
+    <!--<input type="text" value="https://yolo:amk@bridge.mmnx.de/videos/readfile.php?file=/media/Serien/American Dad/S01/American.Dad-S01E01-Pilot.mp4" /> <sub>Kopieren für Wiedergabe in Medienplayer (Netzwerkstream)</sub>-->
+
+    <div style="background: gray; text-align: center;">
+      <video width="auto" height="auto" controls>
+        <source src="readfile.php?file=<?php echo urlencode(base64_encode($this->_['videoFile'])); ?>" type="video/mp4">Your browser does not support the video tag.</video>
+    </div>
+  </div>
+</div>

+ 4 - 0
templates/footer.php

@@ -0,0 +1,4 @@
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" integrity="sha512-K1qjQ+NcF2TYO/eI3M6v8EiNYZfA95pQumfvcVrTHtwQVDG+aHRqLi/ETn2uB+1JqwYqVG3LIvdm9lj6imS/pQ==" crossorigin="anonymous"></script>
+    </body>
+</html>

+ 102 - 0
templates/header.php

@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html lang="de">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    <title>moeFlix</title>
+
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous">
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
+
+    <style>
+
+      h1 {
+        font-size: 8vh;
+      }
+
+      #logo {
+        margin: 0 auto;
+        text-align: center;
+      }
+
+      #logo img {
+      width: 100%;
+      }
+
+      @media only screen and (min-width : 768px) {
+        #logo img {
+          width: auto;
+          height: 200px;
+        }
+      }
+
+      .col-sm-3 > div > a > img {
+        width: 100%;
+      }
+
+      .col-sm-3 {
+        padding-bottom: 15px;
+        min-height: 500px;
+      }
+
+      .col-sm-3.episode {
+        min-height: 50px;
+      }
+
+      .col-sm-3.episode > div > a > img {
+        min-height: 50px;
+        max-height: 170px;
+      }
+
+      .col-sm-3.folder-up-episode img {
+        max-height: 225px;
+      }
+
+      .image-zoom {
+        overflow: hidden;
+      }
+
+      .image-zoom img {
+        -webkit-transition: all 1s ease; /* Safari and Chrome */
+        -moz-transition: all 1s ease; /* Firefox */
+        -ms-transition: all 1s ease; /* IE 9 */
+        -o-transition: all 1s ease; /* Opera */
+        transition: all 1s ease;
+      }
+
+      .image-zoom:hover img {
+        -webkit-transform:scale(1.15); /* Safari and Chrome */
+        -moz-transform:scale(1.15); /* Firefox */
+        -ms-transform:scale(1.15); /* IE 9 */
+        -o-transform:scale(1.15); /* Opera */
+        transform:scale(1.15);
+      }
+
+      .episode-title {
+        padding-top: 15px;
+      }
+
+      #user-menu {
+        text-align: center;
+      }
+
+      @media only screen and (max-width : 480px) {
+        .col-sm-3 {
+          text-align: center;
+        }
+      }
+
+    </style>
+  </head>
+  <body>
+
+    <div id="logo">
+      <a href="/"><img src="/img/moeflix.png" alt="moeflix"></a>
+    </div>
+    <div id="user-menu">
+      <span>
+        <i class="fa fa-hand-peace-o"></i>, <a href="?view=user"><?php echo $_SESSION['mail']; ?></a> <a href="/?action=logout">Logout</a>
+      </span>
+    </div>

+ 71 - 0
templates/login.php

@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html lang="de">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    <title>Login</title>
+
+    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous">
+    <style>
+      body {
+        padding-top: 40px;
+        padding-bottom: 40px;
+        background-color: #eee;
+      }
+
+      .form-signin {
+        max-width: 330px;
+        padding: 15px;
+        margin: 0 auto;
+      }
+      .form-signin .form-signin-heading,
+      .form-signin .checkbox {
+        margin-bottom: 10px;
+      }
+      .form-signin .checkbox {
+        font-weight: normal;
+      }
+      .form-signin .form-control {
+        position: relative;
+        height: auto;
+        -webkit-box-sizing: border-box;
+           -moz-box-sizing: border-box;
+                box-sizing: border-box;
+        padding: 10px;
+        font-size: 16px;
+      }
+      .form-signin .form-control:focus {
+        z-index: 2;
+      }
+      .form-signin input[type="email"] {
+        margin-bottom: -1px;
+        border-bottom-right-radius: 0;
+        border-bottom-left-radius: 0;
+      }
+      .form-signin input[type="password"] {
+        margin-bottom: 10px;
+        border-top-left-radius: 0;
+        border-top-right-radius: 0;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="container">
+      <form class="form-signin" action="?action=login" method="post">
+        <h2 class="form-signin-heading">Please sign in</h2>
+        <label for="inputEmail" class="sr-only">Email address</label>
+        <input type="email" id="inputEmail" class="form-control" placeholder="Email address" name="mail" required autofocus>
+        <label for="inputPassword" class="sr-only">Password</label>
+        <input type="password" id="inputPassword" class="form-control" placeholder="Password" name="pass" required>
+        <!--<div class="checkbox">
+          <label>
+            <input type="checkbox" value="remember-me"> Remember me
+          </label>
+        </div>--><!--TODO: implement Remember me (Cookie) -->
+        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+      </form>
+    </div>
+  </body>
+</html>

+ 3 - 0
templates/matrix.php

@@ -0,0 +1,3 @@
+<?php echo $this->_['header']; ?>
+<?php echo $this->_['content']; ?>
+<?php echo $this->_['footer']; ?>

+ 11 - 0
templates/movie.php

@@ -0,0 +1,11 @@
+<div class="container-fluid">
+  <h1><?php echo $this->_['movie'][0]['name']; ?></h1>
+  <div class="row">
+    <!--<input type="text" value="https://yolo:amk@bridge.mmnx.de/videos/readfile.php?file=/media/Serien/American Dad/S01/American.Dad-S01E01-Pilot.mp4" /> <sub>Kopieren für Wiedergabe in Medienplayer (Netzwerkstream)</sub>-->
+
+    <div style="background: gray; text-align: center;">
+      <video width="auto" height="auto" controls>
+        <source src="readfile.php?file=<?php echo urlencode(base64_encode($this->_['videoFile'])); ?>" type="video/mp4">Your browser does not support the video tag.</video>
+    </div>
+  </div>
+</div>

+ 5 - 0
templates/scrape.php

@@ -0,0 +1,5 @@
+<?php
+
+echo $this->_['scraper']->getOutputText();
+
+?>

+ 19 - 0
templates/season.php

@@ -0,0 +1,19 @@
+<div class="container-fluid">
+  <h1>Staffeln</h1>
+  <div class="row">
+    <?php foreach($this->_['episodes'] as $episode) { ?>
+
+    <div class="col-sm-3 episode">
+      <div class="image-zoom">
+        <a href="?view=episode&episodeID=<?php echo $episode['id']; ?>">
+          <img class="img-responsive" src="/img/posters/<?php echo $episode['thumb']; ?>" alt="<?php echo $episode['number']; ?>">
+          <div class="episode-title"><?php echo $episode['number']; ?> - <?php echo $episode['name']; ?></div>
+        </a>
+      </div>
+    </div>
+
+    <?php
+    }
+    ?>
+  </div>
+</div>

+ 22 - 0
templates/seasons.php

@@ -0,0 +1,22 @@
+<div class="container-fluid">
+  <h1>Staffeln</h1>
+  <div class="row">
+    <?php foreach($this->_['seasons'] as $season) {
+      if($season['enabled'] == 0) {
+        continue;
+      }
+    ?>
+
+    <div class="col-sm-3">
+      <div class="image-zoom">
+        <a href="?view=season&seasonID=<?php echo $season['id']; ?>">
+          <img class="img-responsive" src="/img/posters/<?php echo $season['poster']; ?>" alt="<?php echo $season['number']; ?>">
+        </a>
+      </div>
+    </div>
+
+    <?php
+    }
+    ?>
+  </div>
+</div>

+ 34 - 0
templates/user.php

@@ -0,0 +1,34 @@
+<div class="container-fluid">
+  <div class="row">
+    <div class="col-md-12">
+      <h1>Settings</h1>
+
+      <form class="form-horizontal" action="?view=user&action=updateUserInfo" method="post">
+        <div class="form-group">
+          <label for="newPassword" class="col-sm-2 control-label">New Password<br>(Leave empty to keep current)</label>
+          <div class="col-sm-6">
+            <input type="password" class="form-control" id="newPassword" name="newPassword" placeholder="p455w0rd">
+          </div>
+        </div>
+        <div class="form-group">
+            <label for="newPasswordConfirmation" class="col-sm-2 control-label">New Password (Confirmation)</label>
+            <div class="col-sm-6">
+              <input type="password" class="form-control" id="newPasswordConfirmation" name="newPasswordConfirmation" placeholder="p455w0rd">
+            </div>
+        </div>
+        <div class="form-group">
+            <label for="email" class="col-sm-2 control-label">Email address</label>
+            <div class="col-sm-6">
+              <input type="email" class="form-control" id="email" name="newEmail" placeholder="user@host.tld" value="<?php echo $GLOBALS['user']->getMail(); ?>">
+              <input type="hidden" name="oldEmail" value="<?php echo $GLOBALS['user']->getMail(); ?>">
+            </div>
+        </div>
+        <div class="form-group">
+          <div class="col-sm-offset-2 col-sm-10">
+            <button type="submit" class="btn btn-default">Save</button>
+          </div>
+        </div>
+      </form>
+    </div>
+  </div>
+</div>