From 0eb805d76b37a5990ba07413f44c8130d2edb751 Mon Sep 17 00:00:00 2001 From: Jurian Sluiman Date: Tue, 17 Sep 2013 12:06:39 +0200 Subject: [PATCH] Create view helpers hooking into ASSEMBLE event Create a new event for the detector ("assemble") to assemble localised urls. Create two view helpers: one to create a single localised url for a given route and another view helper to create a listing of all available languages and the appropriate urls to the current page in the given language. Every strategy can support assembling and update their code accordingly. --- config/module.config.php | 11 + src/SlmLocale/Locale/Detector.php | 25 ++ src/SlmLocale/LocaleEvent.php | 30 +- .../Service/LocaleMenuViewHelperFactory.php | 62 ++++ .../Service/LocaleUrlViewHelperFactory.php | 65 ++++ src/SlmLocale/Strategy/AbstractStrategy.php | 11 +- src/SlmLocale/Strategy/UriPathStrategy.php | 14 + src/SlmLocale/View/Helper/LocaleMenu.php | 312 ++++++++++++++++++ src/SlmLocale/View/Helper/LocaleUrl.php | 117 +++++++ 9 files changed, 642 insertions(+), 5 deletions(-) create mode 100644 src/SlmLocale/Service/LocaleMenuViewHelperFactory.php create mode 100644 src/SlmLocale/Service/LocaleUrlViewHelperFactory.php create mode 100644 src/SlmLocale/View/Helper/LocaleMenu.php create mode 100644 src/SlmLocale/View/Helper/LocaleUrl.php diff --git a/config/module.config.php b/config/module.config.php index 5d55d11..1420d13 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -51,4 +51,15 @@ 'SlmLocale\Locale\Detector' => 'SlmLocale\Service\DetectorFactory', ), ), + + 'view_helpers' => array( + 'aliases' => array( + 'localeUrl' => 'SlmLocale\View\Helper\LocaleUrl', + 'localeMenu' => 'SlmLocale\View\Helper\LocaleMenu', + ), + 'factories' => array( + 'SlmLocale\View\Helper\LocaleUrl' => 'SlmLocale\Service\LocaleUrlViewHelperFactory', + 'SlmLocale\View\Helper\LocaleMenu' => 'SlmLocale\Service\LocaleMenuViewHelperFactory', + ), + ), ); \ No newline at end of file diff --git a/src/SlmLocale/Locale/Detector.php b/src/SlmLocale/Locale/Detector.php index 108b668..d5c99c7 100644 --- a/src/SlmLocale/Locale/Detector.php +++ b/src/SlmLocale/Locale/Detector.php @@ -46,6 +46,7 @@ use Zend\EventManager\EventManagerInterface; use Zend\Stdlib\RequestInterface; use Zend\Stdlib\ResponseInterface; +use Zend\Uri\Uri; class Detector implements EventManagerAwareInterface { @@ -169,4 +170,28 @@ public function detect(RequestInterface $request, ResponseInterface $response = return $locale; } + + public function assemble($locale, $uri) + { + $event = new LocaleEvent(LocaleEvent::EVENT_ASSEMBLE, $this); + $event->setLocale($locale); + + if ($this->hasSupported()) { + $event->setSupported($this->getSupported()); + } + + if (!$uri instanceof Uri) { + $uri = new Uri($uri); + } + $event->setUri($uri); + + $events = $this->getEventManager(); + $results = $events->trigger($event); + + if (!$results->stopped()) { + return $uri; + } + + return $results->last(); + } } diff --git a/src/SlmLocale/LocaleEvent.php b/src/SlmLocale/LocaleEvent.php index 3b0ad2a..ae06855 100644 --- a/src/SlmLocale/LocaleEvent.php +++ b/src/SlmLocale/LocaleEvent.php @@ -43,16 +43,19 @@ use Zend\EventManager\Event; use Zend\Stdlib\RequestInterface; use Zend\Stdlib\ResponseInterface; +use Zend\Uri\Uri; class LocaleEvent extends Event { - const EVENT_DETECT = 'detect'; - const EVENT_FOUND = 'found'; + const EVENT_DETECT = 'detect'; + const EVENT_FOUND = 'found'; + const EVENT_ASSEMBLE = 'assemble'; protected $request; protected $response; protected $supported; protected $locale; + protected $uri; public function getRequest() { @@ -106,4 +109,27 @@ public function setLocale($locale) $this->locale = $locale; return $this; } + + /** + * Get uri for assemble event + * + * @return Uri + */ + public function getUri() + { + return $this->uri; + } + + /** + * Set uri for assemble event + * + * @param Uri $uri + * @return self + */ + public function setUri(Uri $uri) + { + $this->setParam('uri', $uri); + $this->uri = $uri; + return $this; + } } \ No newline at end of file diff --git a/src/SlmLocale/Service/LocaleMenuViewHelperFactory.php b/src/SlmLocale/Service/LocaleMenuViewHelperFactory.php new file mode 100644 index 0000000..237a227 --- /dev/null +++ b/src/SlmLocale/Service/LocaleMenuViewHelperFactory.php @@ -0,0 +1,62 @@ + + * @copyright 2012-2013 Jurian Sluiman. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://juriansluiman.nl + */ + +namespace SlmLocale\Service; + +use SlmLocale\View\Helper\LocaleMenu; +use Zend\ServiceManager\FactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; + +class LocaleMenuViewHelperFactory implements FactoryInterface +{ + /** + * @param ServiceLocatorInterface $serviceLocator + * @return LocaleMenu + */ + public function createService(ServiceLocatorInterface $serviceLocator) + { + $detector = $serviceLocator->getServiceLocator()->get('SlmLocale\Locale\Detector'); + + $helper = new LocaleMenu; + $helper->setDetector($detector); + + return $helper; + } +} \ No newline at end of file diff --git a/src/SlmLocale/Service/LocaleUrlViewHelperFactory.php b/src/SlmLocale/Service/LocaleUrlViewHelperFactory.php new file mode 100644 index 0000000..79f2c71 --- /dev/null +++ b/src/SlmLocale/Service/LocaleUrlViewHelperFactory.php @@ -0,0 +1,65 @@ + + * @copyright 2012-2013 Jurian Sluiman. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://juriansluiman.nl + */ + +namespace SlmLocale\Service; + +use SlmLocale\View\Helper\LocaleUrl; +use Zend\ServiceManager\FactoryInterface; +use Zend\ServiceManager\ServiceLocatorInterface; + +class LocaleUrlViewHelperFactory implements FactoryInterface +{ + /** + * @param ServiceLocatorInterface $serviceLocator + * @return LocaleUrl + */ + public function createService(ServiceLocatorInterface $serviceLocator) + { + $sl = $serviceLocator->getServiceLocator(); + + $detector = $sl->get('SlmLocale\Locale\Detector'); + $request = $sl->get('Request'); + $app = $sl->get('Application'); + + $match = $app->getMvcEvent()->getRouteMatch(); + $helper = new LocaleUrl($detector, $request, $match); + return $helper; + } +} \ No newline at end of file diff --git a/src/SlmLocale/Strategy/AbstractStrategy.php b/src/SlmLocale/Strategy/AbstractStrategy.php index 27fd35b..1683b6a 100644 --- a/src/SlmLocale/Strategy/AbstractStrategy.php +++ b/src/SlmLocale/Strategy/AbstractStrategy.php @@ -55,15 +55,16 @@ abstract class AbstractStrategy implements StrategyInterface protected $listeners = array(); /** - * Attach "detect" and "found" listeners + * Attach "detect", "found" and "assemble" listeners * * @param EventManagerInterface $events * @param int $priority */ public function attach(EventManagerInterface $events, $priority = 1) { - $this->listeners[] = $events->attach(LocaleEvent::EVENT_DETECT, array($this, 'detect'), $priority); - $this->listeners[] = $events->attach(LocaleEvent::EVENT_FOUND, array($this, 'found'), $priority); + $this->listeners[] = $events->attach(LocaleEvent::EVENT_DETECT, array($this, 'detect'), $priority); + $this->listeners[] = $events->attach(LocaleEvent::EVENT_FOUND, array($this, 'found'), $priority); + $this->listeners[] = $events->attach(LocaleEvent::EVENT_ASSEMBLE, array($this, 'assemble'), $priority); } /** @@ -88,6 +89,10 @@ public function found(LocaleEvent $event) { } + public function assemble(LocaleEvent $event) + { + } + protected function isHttpRequest(RequestInterface $request) { return $request instanceof HttpRequest; diff --git a/src/SlmLocale/Strategy/UriPathStrategy.php b/src/SlmLocale/Strategy/UriPathStrategy.php index 3b1b97a..bb1c793 100644 --- a/src/SlmLocale/Strategy/UriPathStrategy.php +++ b/src/SlmLocale/Strategy/UriPathStrategy.php @@ -40,6 +40,7 @@ namespace SlmLocale\Strategy; +use Locale; use SlmLocale\LocaleEvent; use Zend\ServiceManager\ServiceLocatorInterface; use Zend\ServiceManager\ServiceLocatorAwareInterface; @@ -188,6 +189,19 @@ public function found(LocaleEvent $event) return $response; } + public function assemble(LocaleEvent $event) + { + $current = Locale::getDefault(); + $locale = $event->getLocale(); + $uri = $event->getUri(); + $path = $uri->getPath(); + + $path = str_replace($current, $locale, $path); + $uri->setPath($path); + + return $uri; + } + protected function getFirstSegmentInPath(RequestInterface $request, $base = null) { $path = $request->getUri()->getPath(); diff --git a/src/SlmLocale/View/Helper/LocaleMenu.php b/src/SlmLocale/View/Helper/LocaleMenu.php new file mode 100644 index 0000000..7058f52 --- /dev/null +++ b/src/SlmLocale/View/Helper/LocaleMenu.php @@ -0,0 +1,312 @@ + + * @copyright 2012-2013 Jurian Sluiman. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://juriansluiman.nl + */ + +namespace SlmLocale\View\Helper; + +use Locale; +use SlmLocale\Locale\Detector; +use Zend\View\Helper\AbstractHelper; +use Zend\View\Exception\RuntimeException; + +class LocaleMenu extends AbstractHelper +{ + /** + * @var Detector $detector + */ + protected $detector; + + /** + * Set the class to be used on the list container + * + * @var string || null + */ + protected $class; + + /** + * Method used to construct a title for each item + * + * @var string || null + */ + protected $titleMethod = 'displayLanguage'; + + /** + * Flag to specify specifies whether the title should be in the current locale + * + * @var boolean default false + */ + protected $titleInCurrentLocale = false; + + /** + * Method used to construct a label for each item + * + * @var string || null + */ + protected $labelMethod = 'displayLanguage'; + + /** + * Flag to specify specifies whether the label should be in the current locale + * + * @var boolean default true + */ + protected $labelInCurrentLocale = true; + + /** + * Flag to specify the current locale should be omitted from the menu + * + * @var boolean default false + */ + protected $omitCurrent = false; + + /** + * @param Detector $detector + */ + public function setDetector($detector) + { + $this->detector = $detector; + } + + /** + * @return Detector $detector + */ + public function getDetector() + { + return $this->detector; + } + + /** + * @param string $class + */ + public function setUlClass($class) + { + $this->class = $class; + return $this; + } + + /** + * @return string + */ + public function getUlClass() + { + return $this->class; + } + + /** + * @param string $itemTitleMethod + */ + public function setTitleMethod($titleMethod) + { + $this->checkLocaleMethod($titleMethod); + + $this->titleMethod = $titleMethod; + return $this; + } + + /** + * @return string + */ + public function getTitleMethod() + { + return $this->titleMethod; + } + + /** + * @param boolean $flag + */ + public function setTitleInCurrentLocale($flag) + { + $this->titleInCurrentLocale = (bool) $flag; + return $this; + } + + /** + * @return boolean + */ + public function getTitleInCurrentLocale() + { + return $this->titleInCurrentLocale; + } + + /** + * @param string $labelMethod + */ + public function setLabelMethod($labelMethod) + { + $this->checkLocaleMethod($labelMethod); + + $this->labelMethod = $labelMethod; + return $this; + } + + /** + * @return string + */ + public function getLabelMethod() + { + return $this->labelMethod; + } + + /** + * @param boolean $flag + */ + public function setLabelInCurrentLocale($flag) + { + $this->labelInCurrentLocale = (bool) $flag; + return $this; + } + + /** + * @return boolean + */ + public function getLabelInCurrentLocale() + { + return $this->labelInCurrentLocale; + } + + /** + * @param boolean $omitCurrent + */ + public function setOmitCurrent($omitCurrent) + { + $this->omitCurrent = (bool) $omitCurrent; + return $this; + } + + /** + * @return boolean + */ + public function omitCurrent() + { + return $this->omitCurrent; + } + + public function __invoke() + { + return $this; + } + + /** + * @param array $options + * @return string + * @throws RuntimeException + * @todo implement add way to completely default rendering for maximum flexibility (see Zend\View\Helper\Navigation::renderPartial) + */ + public function __toString() + { + if (!($detector = $this->getDetector())) { + throw new RuntimeException('To assemble an url, a detector is required'); + } + + $list = ''; + $current = Locale::getDefault(); + foreach($detector->getSupported() as $locale) { + if ($this->omitCurrent() && $current === $locale) { + continue; + } + + $titleLocale = $this->getTitleInCurrentLocale() ? $locale : $current; + $labelLocale = $this->getLabelInCurrentLocale() ? $locale : $current; + + $url = $this->getView()->localeUrl($locale); + $title = $this->callLocale($this->getTitleMethod(), $locale, $titleLocale); + $label = $this->callLocale($this->getLabelMethod(), $locale, $labelLocale); + + $item = sprintf( + '
  • %s
  • ' . "\n", + $url, + $title, + ($current === $locale) ? ' class="active"' : '', + $label + ); + + $list .= $item; + } + + $class = $this->getUlClass(); + $html = sprintf( + '%s', + ($class) ? sprintf(' class="%s"', $class) : '', + $list + ); + + return $html; + } + + /** + * Check whether method part of the Locale class is + * + * @param string $method Method to check + * @throws RuntimeException If method is not part of locale + * @return true + */ + protected function checkLocaleMethod($method) + { + $options = array( + 'displayLanguage', + 'displayName', + 'displayRegion', + 'displayScript', + 'displayVariant', + 'primaryLanguage', + 'region', + 'script' + ); + + if (!in_array($method, $options)) { + throw new RuntimeException(sprintf( + 'Unknown method "%s" for Locale, expecting one of these: %s.', + $method, + implode(', ', $options) + )); + } + } + + protected function callLocale($method, $locale, $in_locale = false) + { + $method = sprintf('\Locale::get%s', ucfirst($method)); + + $args = array($locale); + + if ($in_locale && !in_array($method, array('primaryLanguage', 'region', 'script'))) { + $args[] = $in_locale; + } + + return call_user_func_array($method, $args); + } +} \ No newline at end of file diff --git a/src/SlmLocale/View/Helper/LocaleUrl.php b/src/SlmLocale/View/Helper/LocaleUrl.php new file mode 100644 index 0000000..d2fc0ad --- /dev/null +++ b/src/SlmLocale/View/Helper/LocaleUrl.php @@ -0,0 +1,117 @@ + + * @copyright 2012-2013 Jurian Sluiman. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @link http://juriansluiman.nl + */ +namespace SlmLocale\View\Helper; + +use SlmLocale\Locale\Detector; +use Zend\Http\Request; +use Zend\Mvc\Router\Http\RouteMatch; +use Zend\View\Helper\AbstractHelper; +use Zend\View\Exception\RuntimeException; + +class LocaleUrl extends AbstractHelper +{ + /** + * @var Detector $detector + */ + protected $detector; + + protected $match; + + /** + * @var Request $request + */ + protected $request; + + public function __construct(Detector $detector, Request $request, Routematch $match = null) + { + $this->detector = $detector; + $this->match = $match; + $this->request = $request; + } + + protected function getDetector() + { + return $this->detector; + } + + protected function getRouteMatch() + { + return $this->match; + } + + protected function getRequest() + { + return $this->request; + } + + /** + * Generates an localized url + * + * @see Zend\View\Helpes\Url::__invoke() + * @param string $locale Locale + * @param string $name Name of the route + * @param array $params Parameters for the link + * @param array $options Options for the route + * @param boolean $reuseMatchedParams Whether to reuse matched parameters + * @return string Url For the link href attribute + * @throws Exception\RuntimeException If no RouteStackInterface was provided + * @throws Exception\RuntimeException If no RouteMatch was provided + * @throws Exception\RuntimeException If RouteMatch didn't contain a matched route name + */ + public function __invoke($locale, $name = null, $params = array(), $options = array(), $reuseMatchedParams = true) + { + if (!$this->getDetector()) { + throw new RuntimeException('To assemble an url, a detector is required'); + } + + /** + * With a route match, we can use the url view helper to assemble a new url. If no + * route match is present, we've a 404 and grab the path from the request object. + */ + if ($this->getRouteMatch()) { + $url = $this->getView()->url($name, $params, $options, $reuseMatchedParams); + } else { + $url = $this->getRequest()->getUri()->getPath(); + } + + return $this->getDetector()->assemble($locale, $url); + } + +} \ No newline at end of file