* keywords im content mit Links ersetzen
* Links mit Klasse "show-modal" bei Klick in einen JS-Modal laden, statt dem Link zu folgen git-svn-id: http://78.47.251.156/svn/dev/sterntours-3@3296 f459cee4-fb09-11de-96c3-f9c5f16c3c76
This commit is contained in:
parent
20beca7c4d
commit
c924b4af15
15 changed files with 333 additions and 75 deletions
1
trunk/app/Resources/views/ajax.html.twig
Normal file
1
trunk/app/Resources/views/ajax.html.twig
Normal file
|
|
@ -0,0 +1 @@
|
|||
{% block body %}{% endblock %}
|
||||
|
|
@ -103,6 +103,9 @@
|
|||
|
||||
</div><!-- end wrapper -->
|
||||
|
||||
<!-- default modal -->
|
||||
{% embed 'default/components/embed/modal.html.twig' with {id: 'default'} %}{% endembed %}
|
||||
|
||||
{% block javascripts %}
|
||||
<script src="http://maps.google.com/maps/api/js?sensor=false"></script>
|
||||
{% javascripts
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">{{ title }}</h4>
|
||||
<h4 class="modal-title">{{ title ?? '' }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% block body %}{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
{% extends get_base_template() %}
|
||||
|
||||
{% block body %}
|
||||
<section class="clearfix">
|
||||
{{ page.content|raw }}
|
||||
{{ page.content|raw|keywords }}
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
{% extends get_base_template() %}
|
||||
|
||||
{#
|
||||
{% block nav_sidebar_widget %}
|
||||
|
|
@ -21,6 +21,6 @@
|
|||
</section>
|
||||
|
||||
<section class="clearfix">
|
||||
{{ page.content|raw }}
|
||||
{{ page.content|raw|keywords }}
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
{% extends get_base_template() %}
|
||||
|
||||
{% block body %}
|
||||
<section class="clearfix">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{# @var travel_program \AppBundle\Entity\TravelProgram #}
|
||||
{% extends 'base.html.twig' %}
|
||||
{% extends get_base_template() %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
|
|
@ -116,7 +116,7 @@
|
|||
|
||||
<div role="tabpanel" class="tab-pane active" id="travel-description-content-tab">
|
||||
|
||||
{{ travel_program.htmlDescription|raw }}
|
||||
{{ travel_program.htmlDescription|raw|keywords }}
|
||||
|
||||
{% if travel_program.advices is not empty %}
|
||||
<h3>Hinweise</h3>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
{% extends get_base_template() %}
|
||||
|
||||
{% block body %}
|
||||
<section class="clearfix">
|
||||
|
|
@ -32,6 +32,6 @@
|
|||
</section>
|
||||
|
||||
<section class="clearfix">
|
||||
{{ page.content|raw }}
|
||||
{{ page.content|keywords|raw }}
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
|
@ -11,8 +11,8 @@ services:
|
|||
app.controller_listener:
|
||||
class: AppBundle\Listener\KernelControllerListener
|
||||
arguments:
|
||||
- "@doctrine.orm.entity_manager"
|
||||
- "@controller_resolver"
|
||||
- '@doctrine.orm.entity_manager'
|
||||
- '@controller_resolver'
|
||||
tags:
|
||||
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
|
||||
|
||||
|
|
@ -21,6 +21,8 @@ services:
|
|||
#public: false
|
||||
arguments:
|
||||
- '@twig'
|
||||
- '@app.keyword_service'
|
||||
- '@request_stack'
|
||||
tags:
|
||||
- { name: twig.extension }
|
||||
|
||||
|
|
@ -29,7 +31,7 @@ services:
|
|||
arguments:
|
||||
- '@monolog.logger'
|
||||
|
||||
app.booking_request_validator:
|
||||
class: AppBundle\Validator\BookingRequestValidator
|
||||
tags:
|
||||
- {name: validator.constraint_validator }
|
||||
app.keyword_service:
|
||||
class: AppBundle\Service\KeywordService
|
||||
arguments:
|
||||
- '@doctrine.orm.entity_manager'
|
||||
|
|
@ -1,6 +1,82 @@
|
|||
(function($) {
|
||||
jQuery(document).ready(function($) {
|
||||
"use strict";
|
||||
|
||||
/* ==============================================
|
||||
VIDEOS
|
||||
=============================================== */
|
||||
|
||||
var videos$ = $('a[id^="video_"]');
|
||||
|
||||
function videoInitHandler()
|
||||
{
|
||||
var el$ = $(this);
|
||||
|
||||
var text = el$.text();
|
||||
var length = text.length - 11;
|
||||
var caption = text.substring(0, length);
|
||||
var expl = this.id.substring(6, this.id.length);
|
||||
|
||||
$('<iframe />')
|
||||
.attr({
|
||||
width: 680,
|
||||
height: 466,
|
||||
src: '//www.youtube.com/embed/'+ expl,
|
||||
frameborder: 0,
|
||||
allowfullscreen: true,
|
||||
'data-st-video': this.id
|
||||
})
|
||||
.addClass('st-collapsed')
|
||||
.hide()
|
||||
.insertAfter(this)
|
||||
;
|
||||
el$
|
||||
.css('background-image', 'url(/images/st2/icons/arrowup.gif)')
|
||||
.text(caption + ' einblenden')
|
||||
.attr('href', 'javascript:void(0);')
|
||||
;
|
||||
}
|
||||
|
||||
videos$.each(videoInitHandler);
|
||||
|
||||
videos$.click(function() {
|
||||
|
||||
var el$ = $(this);
|
||||
var video$ = $('[data-st-video='+ this.id +']');
|
||||
var text = el$.text();
|
||||
var length = text.length - 11;
|
||||
var caption = text.substring(0, length);
|
||||
|
||||
if (el$.hasClass('st-collapsed'))
|
||||
{
|
||||
video$.slideDown('slow');
|
||||
el$.text(caption + ' ausblenden');
|
||||
el$.removeClass('st-collapsed');
|
||||
}
|
||||
else
|
||||
{
|
||||
video$.slideUp(400);
|
||||
el$.text(caption + ' einblenden');
|
||||
el$.addClass('st-collapsed');
|
||||
}
|
||||
});
|
||||
|
||||
/* ==============================================
|
||||
KEYWORDS
|
||||
=============================================== */
|
||||
|
||||
var modal$ = $('#st-default-modal');
|
||||
|
||||
$('a.show-layer').click(function() {
|
||||
|
||||
$.get($(this).attr('href')).then(function(r) {
|
||||
|
||||
modal$.find('.modal-body').html(r);
|
||||
modal$.find('a[id^="video_"]').each(videoInitHandler);
|
||||
modal$.modal('show');
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
/* ==============================================
|
||||
HEADER STICKY -->
|
||||
=============================================== */
|
||||
|
|
@ -339,4 +415,4 @@
|
|||
firstDay: 1
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
});
|
||||
|
|
@ -24,59 +24,4 @@ $(document).ready(function() {
|
|||
activateTravelDatesTab();
|
||||
}
|
||||
|
||||
|
||||
|
||||
var videos$ = $('a[id^="video_"]');
|
||||
|
||||
videos$.each(function() {
|
||||
|
||||
var el$ = $(this);
|
||||
|
||||
var text = el$.text();
|
||||
var length = text.length - 11;
|
||||
var caption = text.substring(0, length);
|
||||
var expl = this.id.substring(6, this.id.length);
|
||||
|
||||
$('<iframe />')
|
||||
.attr({
|
||||
width: 680,
|
||||
height: 466,
|
||||
src: '//www.youtube.com/embed/'+ expl,
|
||||
frameborder: 0,
|
||||
allowfullscreen: true,
|
||||
'data-st-video': this.id
|
||||
})
|
||||
.addClass('st-collapsed')
|
||||
.hide()
|
||||
.insertAfter(this)
|
||||
;
|
||||
el$
|
||||
.css('background-image', 'url(/images/st2/icons/arrowup.gif)')
|
||||
.text(caption + ' einblenden')
|
||||
.attr('href', 'javascript:void(0);')
|
||||
;
|
||||
});
|
||||
|
||||
videos$.click(function() {
|
||||
|
||||
var el$ = $(this);
|
||||
var video$ = $('[data-st-video='+ this.id +']');
|
||||
var text = el$.text();
|
||||
var length = text.length - 11;
|
||||
var caption = text.substring(0, length);
|
||||
|
||||
if (el$.hasClass('st-collapsed'))
|
||||
{
|
||||
video$.slideDown('slow');
|
||||
el$.text(caption + ' ausblenden');
|
||||
el$.removeClass('st-collapsed');
|
||||
}
|
||||
else
|
||||
{
|
||||
video$.slideUp(400);
|
||||
el$.text(caption + ' einblenden');
|
||||
el$.addClass('st-collapsed');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
202
trunk/src/AppBundle/Service/KeywordService.php
Normal file
202
trunk/src/AppBundle/Service/KeywordService.php
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Ulrich Hecht <ulrich.hecht@hecht-software.de>
|
||||
* @date 02/17/2017
|
||||
*/
|
||||
|
||||
namespace AppBundle\Service;
|
||||
|
||||
|
||||
use AppBundle\Entity\Keyword;
|
||||
use AppBundle\Entity\Page;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
class KeywordService
|
||||
{
|
||||
protected static $DO_SKIP_KEYWORD_CHECK_BY_DOM_ELEMENT_NAME = ['h1' => true, 'h2' => true, 'h3' => true,
|
||||
'h4' => true, 'h5' => true, 'h6' => true, 'a' => true, 'audio' => true, 'video' => true, 'object' => true,
|
||||
'nav' => true, 'form' => true
|
||||
];
|
||||
|
||||
private $entityManager;
|
||||
private $dictByContext = null;
|
||||
private $globalDict = null;
|
||||
|
||||
/**
|
||||
* KeywordService constructor.
|
||||
*
|
||||
* @param EntityManager $entityManager
|
||||
*/
|
||||
public function __construct(EntityManager $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source: http://78.47.251.156/svn/stern-consulting/sunstarPlugin/branches/1.4/lib/sun.class.php
|
||||
*
|
||||
* @param $html
|
||||
* @param string $classes Space separated values for the "class"-attribute
|
||||
* @return string
|
||||
*/
|
||||
public function addKeywordLinksToHtml($html, $classes = '')
|
||||
{
|
||||
$dict = $this->getKeywordsToLinksDict($classes);
|
||||
$doc = new \DOMDocument('1.0', 'utf-8');
|
||||
$doc->loadHTML('<?xml encoding="utf-8" ?><html><body>'. $html .'</body></html>');
|
||||
$xp = new \DOMXPath($doc);
|
||||
$rootNodes = $xp->query('//body')->item(0)->childNodes;
|
||||
$this->addKeywordLinksToDomNodes($rootNodes, $dict, $doc);
|
||||
$ret = '';
|
||||
foreach($rootNodes as $node)
|
||||
{
|
||||
$ret .= $doc->saveHTML($node);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quelle: http://78.47.251.156/svn/stern-consulting/sunstarPlugin/branches/1.4/lib/sun.class.php
|
||||
*
|
||||
* @param \DOMNodeList|\DOMNode[] &$nodes
|
||||
* @param array &$dict
|
||||
* @param \DOMDocument &$doc
|
||||
*/
|
||||
private function addKeywordLinksToDomNodes(&$nodes, &$dict, &$doc)
|
||||
{
|
||||
// As $nodes is a direct reference to $parentNode->childNodes it will be changed when
|
||||
// replacing text-nodes due to adding keywords. Therefore we loop through this
|
||||
// auxilary array '$originalNodes' which simply contains all nodes to process.
|
||||
$originalNodes = [];
|
||||
foreach ($nodes as $node)
|
||||
{
|
||||
$originalNodes[] = $node;
|
||||
}
|
||||
|
||||
/** @var \DOMNode $node */
|
||||
foreach($originalNodes as $node)
|
||||
{
|
||||
if($node->nodeType == XML_TEXT_NODE)
|
||||
{
|
||||
$nodeValue = $node->nodeValue;
|
||||
$foundKeywords = false;
|
||||
foreach($dict as $keyword => $link)
|
||||
{
|
||||
$nodeValue = preg_replace('/(^|\W)'. preg_quote($keyword, '/') .'($|\W)/u',
|
||||
'$1'. $link .'$2', $nodeValue, 1, $count);
|
||||
if($count > 0)
|
||||
{
|
||||
$foundKeywords = true;
|
||||
unset($dict[$keyword]);
|
||||
}
|
||||
}
|
||||
if($foundKeywords)
|
||||
{
|
||||
$newNode = $doc->createDocumentFragment();
|
||||
$newNode->appendXML($nodeValue);
|
||||
$node->parentNode->replaceChild($newNode, $node);
|
||||
}
|
||||
}
|
||||
elseif($node->nodeType == XML_ELEMENT_NODE &&
|
||||
!isset(self::$DO_SKIP_KEYWORD_CHECK_BY_DOM_ELEMENT_NAME[strtolower($node->nodeName)]))
|
||||
{
|
||||
$classAttr = $node->attributes->getNamedItem("class");
|
||||
if (!(isset($classAttr) && $classAttr->nodeValue &&
|
||||
preg_match('/(^|\s)skip-keywords($|\s)/', $classAttr->nodeValue) == 1))
|
||||
{
|
||||
$this->addKeywordLinksToDomNodes($node->childNodes, $dict, $doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Source: http://78.47.251.156/svn/stern-consulting/sunstarPlugin/branches/1.4/lib/sun.class.php
|
||||
*
|
||||
* @param string $classes Space separated values for the "class"-attribute
|
||||
* @return array
|
||||
*/
|
||||
private function getKeywordsToLinksDict($classes = '', $context = null)
|
||||
{
|
||||
if ($context)
|
||||
{
|
||||
if (!isset($this->dictByContext[$context]))
|
||||
{
|
||||
$this->dictByContext[$context] = null;
|
||||
}
|
||||
$dict = &$this->dictByContext[$context];
|
||||
}
|
||||
else
|
||||
{
|
||||
$dict = &$this->globalDict;
|
||||
}
|
||||
|
||||
if($dict === null)
|
||||
{
|
||||
$dict = [];
|
||||
$keywords = $this->entityManager->getRepository('AppBundle:Keyword')->findAll();
|
||||
/** @var Keyword $keyword */
|
||||
foreach($keywords as $keyword)
|
||||
{
|
||||
if(isset($dict[$keyword->getValue()]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$dictEntry = $this->createKeywordDictEntry($keyword->getUrl(), $keyword->getValue(), $classes);
|
||||
if ($dictEntry !== null)
|
||||
{
|
||||
$dict[$keyword->getValue()] = $dictEntry;
|
||||
}
|
||||
}
|
||||
|
||||
$pages = $this->entityManager->createQueryBuilder()
|
||||
->from('AppBundle:Page', 'p')
|
||||
->select('p')
|
||||
->where('p.keyword IS NOT NULL')
|
||||
->getQuery()
|
||||
->execute()
|
||||
;
|
||||
/** @var Page $page */
|
||||
foreach($pages as $page)
|
||||
{
|
||||
if(isset($dict[$page->getKeyword()]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$dictEntry = $this->createKeywordDictEntry($page->getUrlPath(), $page->getKeyword(), $classes);
|
||||
if ($dictEntry !== null)
|
||||
{
|
||||
$dict[$page->getKeyword()] = $dictEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $dict;
|
||||
}
|
||||
|
||||
private function createKeywordDictEntry($url, $keywordValue, $classes)
|
||||
{
|
||||
$port = parse_url($url, PHP_URL_PORT);
|
||||
if($url[0] == '/' || strtolower($_SERVER['HTTP_HOST']) ==
|
||||
strtolower(parse_url($url, PHP_URL_HOST) .($port ? ':'. $port : '')))
|
||||
{
|
||||
// Same host => check if same page
|
||||
$urlPath = parse_url($url, PHP_URL_PATH);
|
||||
if($_SERVER['REQUEST_URI'] == $urlPath ||
|
||||
($_SERVER['REQUEST_URI'] == '/' && $urlPath == ''))
|
||||
{
|
||||
// Yes => we don't want to link to the current URL
|
||||
return null;
|
||||
}
|
||||
// No => setup a JS popup layer
|
||||
$attr = '';
|
||||
$additional_classes = 'intern-link show-layer ';
|
||||
}
|
||||
else
|
||||
{
|
||||
$additional_classes = '';
|
||||
$attr = 'target="_blank"';
|
||||
}
|
||||
return '<a href="'. $url .'" '. $attr .' class="'. $additional_classes . $classes .'">'. $keywordValue .'</a>';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,14 +7,21 @@
|
|||
namespace AppBundle\Twig;
|
||||
|
||||
|
||||
use AppBundle\Service\KeywordService;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class AppExtension extends \Twig_Extension
|
||||
{
|
||||
protected $environment;
|
||||
private $environment;
|
||||
private $keywordService;
|
||||
private $requestStack;
|
||||
private $template;
|
||||
|
||||
public function __construct(\Twig_Environment $env)
|
||||
public function __construct(\Twig_Environment $env, KeywordService $keywordService, RequestStack $requestStack)
|
||||
{
|
||||
$this->environment = $env;
|
||||
$this->keywordService = $keywordService;
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
|
|
@ -23,6 +30,9 @@ class AppExtension extends \Twig_Extension
|
|||
'stripslashes' => new \Twig_SimpleFilter('stripslashes', [$this, 'stripslashesFilter'], [
|
||||
'is_safe' => ['html']
|
||||
]),
|
||||
'keywords' => new \Twig_SimpleFilter('keywords', [$this, 'keywordsFilter'], [
|
||||
'is_safe' => ['html']
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +45,7 @@ class AppExtension extends \Twig_Extension
|
|||
'form_field_pho' => new \Twig_SimpleFunction('form_field_pho', [$this, 'formFieldPho'], [
|
||||
'is_safe' => ['html']
|
||||
]),
|
||||
'get_base_template' => new \Twig_SimpleFunction('get_base_template', [$this, 'getBaseTemplate']),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +54,16 @@ class AppExtension extends \Twig_Extension
|
|||
return stripslashes($v);
|
||||
}
|
||||
|
||||
public function keywordsFilter($v)
|
||||
{
|
||||
return $this->keywordService->addKeywordLinksToHtml($v, 'keyword-link');
|
||||
}
|
||||
|
||||
public function getBaseTemplate()
|
||||
{
|
||||
return ($this->requestStack->getCurrentRequest()->isXmlHttpRequest() ? 'ajax' : 'base') .'.html.twig';
|
||||
}
|
||||
|
||||
public function formField($form, $label = null, $opt = null)
|
||||
{
|
||||
$this->template = $this->environment->loadTemplate( '::default/form/helpers.html.twig' );
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
/** @var \Composer\Autoload\ClassLoader $loader */
|
||||
$loader = require __DIR__.'/../app/autoload.php';
|
||||
include_once __DIR__.'/../var/bootstrap.php.cache';
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Debug\Debug;
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
// If you don't want to setup permissions the proper way, just uncomment the following PHP line
|
||||
// read http://symfony.com/doc/current/book/installation.html#checking-symfony-application-configuration-and-setup
|
||||
// for more information
|
||||
|
|
@ -10,6 +12,7 @@ use Symfony\Component\Debug\Debug;
|
|||
|
||||
// This check prevents access to debug front controllers that are deployed by accident to production servers.
|
||||
// Feel free to remove this, extend it, or make something more sophisticated.
|
||||
/*
|
||||
if (isset($_SERVER['HTTP_CLIENT_IP'])
|
||||
|| isset($_SERVER['HTTP_X_FORWARDED_FOR'])
|
||||
|| !(in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1']) || php_sapi_name() === 'cli-server')
|
||||
|
|
@ -17,14 +20,17 @@ if (isset($_SERVER['HTTP_CLIENT_IP'])
|
|||
header('HTTP/1.0 403 Forbidden');
|
||||
exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
|
||||
}
|
||||
//*/
|
||||
|
||||
/** @var \Composer\Autoload\ClassLoader $loader */
|
||||
$loader = require __DIR__.'/../app/autoload.php';
|
||||
|
||||
// Redis loader
|
||||
/*
|
||||
$redisLoader = new \AppBundle\RedisClassLoader('de.sterntours.v3', $loader);
|
||||
$loader->unregister();
|
||||
$redisLoader->register(true);
|
||||
//*/
|
||||
|
||||
Debug::enable();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue