<?php
/*
 * Copyright (c) 2012, webvariants GbR, http://www.webvariants.de
 *
 * This file is released under the terms of the MIT license. You can find the
 * complete text in the attached LICENSE file or online at:
 *
 * http://www.opensource.org/licenses/mit-license.php
 */

/**
 * Sally-Eventlistener
 *
 * Diese Klasse sammelt zu einem Großteil nur Listener, um den URL-Cache immer
 * aktuell zu halten. Für gewöhnlich will niemand diese Methoden direkt
 * aufrufen.
 *
 * @author Christoph
 * @since  forever
 */
abstract class _WV24_Extensions {
	/**
	 * SLY_ART_ADDED: article was created
	 * SLY_ART_COPIED: article was copied
	 *
	 * @param array $params  event parameters
	 */
	public static function artAdded(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;

		if ($params['event'] === 'SLY_ART_COPIED') {
			$art   = $params['subject'];
			$path  = $art->getPath();
			$id    = $art->getId();
			$clang = $art->getClang();
		}
		else { // SLY_ART_COPIED
			$path  = $params['path'];
			$id    = $params['subject'];
			$clang = $params['clang'];
		}

		// rebuild
		self::rebuildSingle($id, $clang, $path);
	}

	/**
	 * SLY_ART_UPDATED: article was renamed
	 *
	 * @param array $params  event parameters
	 */
	public static function artUpdated(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;

		$article = $params['subject'];
		$path    = $article->getPath();
		$id      = $article->getId();
		$clang   = $article->getClang();
		$ident   = $id.'_'.$clang;

		// do nothing if on contentmeta page, since we do the work in artMetaUpdated()
		if (sly_Core::getCurrentControllerName() === 'contentmeta') return;

		// the name of a category's start article is irrelevant, so do nothing if this is a start article
		if ($article->isStartArticle()) return;

		// rebuild
		self::rebuildSingle($id, $clang, $path);
	}

	/**
	 * SLY_ART_DELETED: article was deleted
	 *
	 * @param array $params
	 */
	public static function artDeleted(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;

		$id      = $params['subject']->getId();
		$storage = WV24_Util::getStorage();

		$storage->clearByArticle($id); // affects *all* URLs to this article
	}

	/**
	 * ART_META_FORM_SECTION: Fragment-Formular erzeugen
	 *
	 * @param  array $params  die von Sally übergebenen Parameter
	 * @return sly_Form       das übergebene Subject mit dem angehängten Formularteil
	 */
	public static function artMetaFormSection(array $params) {
		$articleID = (int) $params['id'];
		$article   = sly_Util_Article::findById($articleID);

		if ($article === null) {
			throw new Exception('Es wurde ein unbekannter Artikel angegeben.');
		}

		$fragment = WV24_Util::getFragment($article);
		$form     = $params['subject'];

		require _WV24_PATH.'views/meta.phtml';
		return $form;
	}

	/**
	 * ART_META_UPDATED: Metadaten eines Artikels wurden aktualisiert
	 *
	 * @param array $params  die von Sally übergebenen Parameter
	 */
	public static function artMetaUpdated(array $params) {
		$articleID = (int) $params['id'];
		$clang     = (int) $params['clang'];

		if ($articleID === sly_Core::getSiteStartArticleId()) {
			return;
		}

		$article  = sly_Util_Article::findById($articleID, $clang);
		$fragment = self::getFragment();
		$db       = sly_DB_Persistence::getInstance();

		// Datenbank aktualisieren
		$db->update('article', array('wv24_fragment' => $fragment), array('id' => $articleID, 'clang' => $clang));

		// Den Cache müssen wir selbst leeren.
		sly_Service_Factory::getArticleService()->deleteCache($articleID, $clang);

		if (WV24_Util::hasSiteStartArticle()) {
			$path = $article->getPath();

			if ($article->isStartArticle()) {
				self::rebuildTree($articleID, $clang, $path);
			}
			else {
				self::rebuildSingle($articleID, $clang, $path);
			}
		}

		$flash = sly_Core::getFlashMessage();
		$flash->appendInfo('realURL2-Einstellungen wurden aktualisiert');
	}

	/**
	 * SLY_CAT_ADDED: Kategorie wurde hinzugefügt
	 *
	 * @param array $params  die von Sally übergebenen Parameter
	 */
	public static function catAdded(array $params) {
		self::artAdded($params);
	}

	/**
	 * CAT_FORM_EDIT: Fragment-Formular erzeugen
	 *
	 * @param  array $params  die von Sally übergebenen Parameter
	 * @return string         das übergebene Subject mit dem angehängten Formularteil
	 */
	public static function catFormEdit(array $params) {
		$category   = $params['category'];
		$categoryID = $category->getId();
		$clang      = $category->getClang();
		$fragment   = WV24_Util::getFragment($category);
		$skipped    = WV24_Util::isSkipped($category);

		ob_start();
		include _WV24_PATH.'views/category.phtml';
		return $params['subject'].ob_get_clean();
	}

	/**
	 * SLY_CAT_UPDATED: Kategorie wurde umbenannt
	 *
	 * @param array $params  die von Sally übergebenen Parameter
	 */
	public static function catUpdated(array $params) {
		$category = $params['subject'];
		$id       = $category->getId();
		$clang    = $category->getClang();

		// API-call-based events should not overwrite database data since no form was submitted
		if (isset($_POST['realurl2_fragment'])) {
			$fragment = self::getFragment();
			$skipped  = sly_post('realurl2_skip', 'boolean', false) ? 1 : 0;
			$db       = sly_DB_Persistence::getInstance();

			// update database
			$data = array('wv24_fragment' => $fragment, 'wv24_skip' => $skipped);
			$db->update('article', $data, array('id' => $id, 'clang' => $clang));
		}

		// Sally leert nur den Cache für Kategorien, nicht aber für den
		// Startartikel der Kategorie.
		sly_Service_Factory::getCategoryService()->deleteCache($id, $clang);

		// do nothing if the 'start category' was changed
		if ($id === sly_Core::getSiteStartArticleId()) {
			return;
		}

		if (WV24_Util::hasSiteStartArticle()) {
			self::rebuildTree($id, $clang);
		}
	}

	/**
	 * SLY_CAT_DELETED: Kategorie wurde gelöscht
	 *
	 * @param array $params  die von Sally übergebenen Parameter
	 */
	public static function catDeleted(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;

		$id      = $params['subject']->getId();
		$storage = WV24_Util::getStorage();

		$storage->clearByArticle($id); // affects *all* URLs to this article
	}

	/**
	 * CLANG_ADDED: Sprache wurde hinzugefügt
	 *
	 * @param array $params  die von Sally übergebenen Parameter
	 */
	public static function clangAdded(array $params) {
		$locale = $params['language']->getLocale();
		$clang  = $params['id'];

		if (empty($locale)) {
			$locale = 'lng'.$clang;
		}
		else {
			$locale = strtolower(str_replace('_', '-', $locale));
		}

		// set the new default fragment for this language

		$base  = mb_substr(sly_Util_HTTP::getBaseUrl(true, 'http'), 5).'/'.$locale;
		$frgmt = WV8_Settings::getSetting('realurl2', 'default_fragment');
		$lower = WV8_Settings::getSetting('realurl2', 'lowercase');
		$clean = WV8_Settings::getSetting('realurl2', 'cleanup');

		$frgmtValues = $frgmt->getValues(true);
		$lowerValues = $lower->getValues(true);
		$cleanValues = $clean->getValues(true);

		$frgmtValues[$clang] = $base;
		$lowerValues[$clang] = 1;
		$cleanValues[$clang] = 1;

		$service = new WV8_Service();
		$service->store($frgmt, $frgmtValues);
		$service->store($lower, $lowerValues);
		$service->store($clean, $cleanValues);

		// generate fresh URLs

		if (WV24_Util::hasSiteStartArticle()) {
			// prepare
			$cache     = new _WV24_RuntimeCache();
			$generator = new _WV24_Generator($cache);
			$storage   = WV24_Util::getStorage();

			// prewarm
			$cache->prewarmAll($clang);

			// generate & store
			$articles = WV24_Util::getTree(null);
			self::rebuildList($articles, $clang, $generator, $storage);
		}
	}

	/**
	 * CLANG_DELETED: Sprache wurde gelöscht
	 *
	 * @param array $params  die von Sally übergebenen Parameter
	 */
	public static function clangDeleted(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;
		$storage = WV24_Util::getStorage();
		$storage->clearByClang($params['id']);
	}

	/**
	 * WV8_SETTINGS_UPDATED: Global Settings wurden geändert
	 *
	 * @param array $params  die von Global Settings übergebenen Parameter
	 */
	public static function settingsUpdated(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;

		// test for equality of all settings
		$generated = array();
		$articles  = null;

		foreach ($params['subject'] as $settingID => $info) {
			if ($info['namespace'] !== 'realurl2') continue;

			if (json_encode($info['old']) !== json_encode($info['new'])) {
				foreach ($info['old'] as $clang => $oldValue) {
					if ($info['new'][$clang] == $oldValue) continue;
					if (in_array($clang, $generated)) continue;

					$generated[] = $clang;

					$cache     = new _WV24_RuntimeCache();
					$generator = new _WV24_Generator($cache);
					$storage   = WV24_Util::getStorage();

					// prewarm
					$cache->prewarmAll($clang);

					// generate & store
					$articles = $articles === null ? WV24_Util::getTree(null) : $articles;
					self::rebuildList($articles, $clang, $generator, $storage);
				}
			}
		}
	}

	/**
	 * SLY_SETTINGS_UPDATED
	 *
	 * @param array $params  die von Sally übergebenen Parameter
	 */
	public static function slySettingsUpdated(array $params) {
		$cur = (int) sly_Core::config()->get('START_ARTICLE_ID');
		$old = isset($params['originals']) ? $params['originals']['START_ARTICLE_ID'] : 0;

		if ($cur > 0 && $cur !== $old) {
			self::cacheCleared(array('subject' => true, 'force' => true));
		}
	}

	/**
	 * SLY_CACHE_CLEARED: Cache wurde geleert
	 *
	 * @param array $params  die von Sally übergebenen Parameter
	 */
	public static function cacheCleared(array $params) {
		WV24_RealURL2::clearCache();

		$isSystem = sly_Core::getCurrentControllerName() === 'system';

		if (!WV24_Util::hasSiteStartArticle()) {
			if ($isSystem) {
				print sly_Helper_Message::warn('Der URL-Cache wurde nicht erzeugt, da die Startseite nicht korrekt konfiguriert ist!');
			}

			return $params['subject'];
		}

		// do nothing if requested
		if (empty($params['force']) && $isSystem) {
			$controller = sly_Core::getCurrentController();

			if (method_exists($controller, 'isCacheSelected') && !$controller->isCacheSelected('realurl2')) {
				return $params['subject'];
			}
		}

		// prepare
		$cache     = new _WV24_RuntimeCache();
		$generator = new _WV24_Generator($cache);
		$storage   = WV24_Util::getStorage();
		$articles  = WV24_Util::getTree(null);

		//should only clear routes for articles
		$storage->clearByNamespace(WV24_RealURL2::URL_NAMESPACE);

		foreach (sly_Util_Language::findAll(true) as $clang) {
			// prewarm
			$cache->prewarmAll($clang);

			// generate & store
			self::rebuildList($articles, $clang, $generator, $storage);
		}

		return $params['subject'];
	}

	/**
	 * OUTPUT_FILTER: Rewriting der Special-URLs aus dem Cache
	 *
	 * Diese Methode läuft über die erzeugte Seite und ersetzt die von Modulen
	 * oder Templates generierten URL-Platzhalter durch die echten URLs.
	 *
	 * Zusätzlich wird diese Methode auch das <base>-Tag im <head> setzen.
	 *
	 * @param  array $params  die von Sally übergebenen Parameter
	 * @return string         der geänderte HTML-Code
	 */
	public static function outputFilter($params) {
		$html = $params['subject'];

		if (WV24_RealURL2::doBaseTag()) {
			$base = WV24_RealURL2::getBaseTag();

			// <base> hinzufügen oder ändern

			if (strpos($html, '<base>') === false && strpos($html, '<base') === false) {
				$html = preg_replace('#<head( .*?)?>#i', '<head$1>'."\n\t".$base, $html);
			}
			else {
				$html = preg_replace('#<base( .*?)?>#i', $base, $html);
			}
		}

		// replace links (%%realurl2://ARTIKEL-CLANG/?params%%)
		preg_match_all('#%%realurl2://([0-9]+)-([0-9]+)/(.*?)%%#i', $html, $matches, PREG_SET_ORDER);

		$hasUrls = WV24_Util::hasSiteStartArticle();

		foreach ($matches as $match) {
			if ($hasUrls) {
				$id     = $match[1];
				$clang  = $match[2];
				$params = $match[3];
				$anchor = '';

				if (mb_strpos($params, '#') !== false) {
					list($params, $anchor) = explode('#', $params, 2);
				}

				// try to restore as much information as possible
				if (!sly_Util_Language::exists($clang)) {
					$clang = sly_Core::getCurrentClang();
				}

				if (!sly_Util_Article::exists($id)) {
					$id = sly_Core::getNotFoundArticleId();
				}

				try {
					$url = WV24_RealURL2::getURL($id, $clang, $params);

					if (mb_strlen($anchor) > 0) {
						$url .= '#'.$anchor;
					}
				}
				catch (Exception $e) {
					$url = '#'.$anchor;
				}
			}
			else {
				$url = '#';
			}

			$html = str_replace($match[0], $url, $html);
		}

		// remove broken links
		$html = preg_replace('#%%realurl2:/+[^%]*%+#i', '#', $html);
		$html = preg_replace('#%%realurl2:/+[^"]*"#i', '#"', $html);

		return $html;
	}

	/**
	 * URL_REWRITE: Ersetzen des URL-Rewriting-Algorithmus
	 *
	 * Diese Methode stellt die Integration von realURL2 in Sally dar. Sie wird
	 * für jede vom CMS erzeugte URL aufgerufen und bietet die Möglichkeit,
	 * entsprechend einzugreifen.
	 *
	 * @param  array $params  die von Sally übergebenen Parameter
	 * @return string         die fertige URL für den Artikel
	 */
	public static function urlRewrite($params) {
		$articleID = (int) $params['id'];
		$clang     = (int) $params['clang'];
		$divider   = $params['divider'];
		$isCaching = $params['disable_cache'];
		$params    = $params['params'];

		if ($isCaching) {
			$params = WV24_Util::buildQuery($params, $divider);
			return '%%realurl2://'.$articleID.'-'.$clang.'/'.$params.'%%';
		}

		if (!WV24_Util::hasSiteStartArticle()) {
			return '#';
		}

		try {
			return WV24_RealURL2::getURL($articleID, $clang, $params, $divider);
		}
		catch (Exception $e) {
			$notFound = sly_Core::getNotFoundArticleId();
			return sly_Util_Article::exists($notFound) ? WV24_RealURL2::getURL($notFound, $clang, $params, $divider) : '#';
		}
	}

	/**
	 * Artikel aus dem aktuellen Request ermitteln
	 *
	 * Diese Methode wird direkt nach dem Einbinden des AddOns ausgeführt und
	 * versucht, die aufgerufene URL zu erkennen und die dazugehörigen
	 * GET-Parameter wiederherzustellen.
	 *
	 * Sollte keine URL gefunden werden, so wird der Extension-Point
	 * REALURL2_CLUELESS ausgeführt. Entsprechende Extensions sollten versuchen,
	 * die URL zu erkennen und bei Erfolg auf die von realURL2 erzeugte URL
	 * weiterleiten.
	 *
	 * Nach der Verarbeitung des Requests (auch bei nicht erkannten, solange
	 * die Extensions in REALURL2_CLUELESS nicht wegsterben, um weiterleiten)
	 * wird der Extension-Point REALURL2_URL_DECODED aufgerufen.
	 */
	public static function resolveArticle(array $params) {
		// someone did the job already
		if ($params['subject']) return $params['subject'];

		$resolver   = new _WV24_Resolver(WV24_Util::getStorage(), sly_Core::cache());
		$dispatcher = new _WV24_Dispatcher($resolver);

		return $dispatcher->dispatch();
	}

	private static function getFragment() {
		$fragment = sly_post('realurl2_fragment', 'string');

		if ($fragment !== '/') {
			$fragment = rtrim($fragment, '/');
		}

		return $fragment;
	}

	public static function articleIsSkipped(array $params) {
		return WV24_Util::isSkipped($params['object']);
	}

	public static function articleGetFragment(array $params) {
		return WV24_Util::getFragment($params['object']);
	}

	public static function bootcache(array $params) {
		sly_Util_BootCache::addClass(__CLASS__);

		if ($params['event'] === 'SLY_BOOTCACHE_CLASSES_FRONTEND') {
			sly_Util_BootCache::addClass('_WV24_Resolver');
			sly_Util_BootCache::addClass('_WV24_Dispatcher');
			sly_Util_BootCache::addClass('WV24_Util');
		}
	}

	/**
	 * SLY_ART_TO_STARTPAGE
	 *
	 * @param array $params  event parameters
	 */
	public static function artToStartpage(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;

		$newStarter = $params['subject'];
		$oldStarter = $params['old_cat'];

		// kill all outdated URLs
		$storage = WV24_Util::getStorage();
		$storage->clearByPath(WV24_RealURL2::URL_NAMESPACE, '%|'.$oldStarter.'|%');
		$storage->clearByArticle($oldStarter);

		// re-generate the tree
		foreach (sly_Util_Language::findAll(true) as $clang) {
			self::rebuildTree($newStarter, $clang, null, false);
		}
	}

	/**
	 * SLY_ART_MOVED
	 *
	 * @param array $params  event parameters
	 */
	public static function artMoved(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;

		$articleID = $params['subject'];
		$clang     = $params['clang'];

		// kill all outdated URLs
		$storage = WV24_Util::getStorage();
		$storage->clearByArticle($articleID, $clang);

		// rebuild
		self::rebuildSingle($articleID, $clang);
	}

	/**
	 * SLY_CAT_MOVED
	 *
	 * @param array $params  event parameters
	 */
	public static function catMoved(array $params) {
		if (!WV24_Util::hasSiteStartArticle()) return;

		$catID = $params['subject'];
		$clang = $params['clang'];

		// kill all outdated URLs
		$storage = WV24_Util::getStorage();
		$storage->clearByPath(WV24_RealURL2::URL_NAMESPACE, '%|'.$catID.'|%');
		$storage->clearByArticle($catID);

		// re-generate the tree
		foreach (sly_Util_Language::findAll(true) as $clang) {
			self::rebuildTree($catID, $clang, null, false);
		}
	}

	/**
	 * SLY_SYSTEM_CACHES
	 *
	 * @param array $params  event parameters
	 */
	public static function systemCacheList(array $params) {
		$select     = $params['subject'];
		$selected   = $select->getValue();
		$selected[] = 'realurl2';

		$select->addValue('realurl2', 'realURL2-Daten neu aufbauen');
		$select->setSelected($selected);

		return $select;
	}

	protected static function rebuildSingle($articleID, $clang, $path = null) {
		$runtimeCache = new _WV24_RuntimeCache();
		$path         = $path === null ? sly_Util_Article::findById($articleID, $clang)->getPath() : $path;

		$runtimeCache->prewarmForArticle($articleID, $clang, $path);

		// get a generator
		$generator = new _WV24_Generator($runtimeCache);
		$url       = $generator->buildUrl($articleID, $clang);

		// store the new URL
		$storage = WV24_Util::getStorage();
		$ident   = $articleID.'_'.$clang;

		$storage->setUrl(WV24_RealURL2::URL_NAMESPACE, $ident, $url, $articleID, $clang, $path);
	}

	protected static function rebuildList(array $articles, $clang, _WV24_Generator $generator, $storage) {
		$urls = array();

		foreach ($articles as $id => $path) {
			$urls[] = array(
				'ident'      => $id.'_'.$clang,
				'article_id' => $id,
				'clang'      => $clang,
				'path'       => $path,
				'url'        => $generator->buildUrl($id, $clang)
			);
		}

		// and store them
		$storage->setMany(WV24_RealURL2::URL_NAMESPACE, $urls);
	}

	protected static function rebuildTree($root, $clang, $path = null, $clear = true) {
		$cache     = new _WV24_RuntimeCache();
		$generator = new _WV24_Generator($cache);
		$storage   = WV24_Util::getStorage();
		$ident     = $root.'_'.$clang;

		if ($path === null) {
			$art  = sly_Util_Article::findById($root, $clang);
			$path = $art->getPath();
		}

		// remove outdated URLs
		$storage->clearByPath(WV24_RealURL2::URL_NAMESPACE, '%|'.$root.'|%', true, $clang);
		$storage->clearByIdent(WV24_RealURL2::URL_NAMESPACE, $ident);

		// prewarm
		$tree = WV24_Util::getTree($root);
		$cache->prewarmForArticle($root, $clang, $path);
		$cache->prewarmArticleData($clang, array_keys($tree));
		$cache->prewarmForSiteStartArticle($clang);

		// generate the URLs
		self::rebuildList($tree, $clang, $generator, $storage);
	}
}
