I want to set the the page title within my extension, so the current {page} object in the Fluid Templates will also show the set title.
$GLOBALS['TSFE']->altPageTitle = $pageTitle; will only set the <title> tag and has no impact to the {page.title}
My Primary Goal: To show the 'correct' title of a detail page within a breadcrumb.
Any ideas how to manipulate that?
I don't know anything about a {page} object in fluid, so I did it with a ViewHelper similar to the one from here
/**
* A view helper for setting the document title in the <title> tag.
*
* = Examples =
*
* <page.title mode="prepend" glue=" - ">{blog.name}</page.title>
*
* <page.title mode="replace">Something here</page.title>
*
* <h1><page.title mode="append" glue=" | " display="render">Title</page.title></h1>
*
* #license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
*/
class PageTitleViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* #var bool
*/
protected $escapeOutput = false;
/**
* #param string $mode Method for adding the new title to the existing one.
* #param string $glue Glue the new title to the old title with this string.
* #param string $display If render, this tag displays it's children. By default it doesn't display anything.
* #return string Rendered content or blank depending on display mode.
* #author Nathan Lenz <nathan.lenz#organicvalley.coop>
*/
public function render($mode = 'replace', $glue = ' - ', $display = 'none') {
$renderedContent = $this->renderChildren();
$existingTitle = $GLOBALS['TSFE']->page['title'];
if ($mode === 'prepend' && !empty($existingTitle)) {
$newTitle = $renderedContent.$glue.$existingTitle;
} else if ($mode === 'append' && !empty($existingTitle)) {
$newTitle = $existingTitle.$glue.$renderedContent;
} else {
$newTitle = $renderedContent;
}
$GLOBALS['TSFE']->page['title'] = $newTitle;
$GLOBALS['TSFE']->indexedDocTitle = $newTitle;
if ($display === 'render') {
return $renderedContent;
} else {
return '';
}
}
}
For cached pages and plugins this should work:
$GLOBALS['TSFE']->page['title'] = $pageTitle;
Related
I have a site that has a dark theme and light theme set in function of the daylight ...
in my constants i define sunset and sunrise and I have a Class that I reach through userFunc, my class:
<?php
namespace Vendor\Extension\Utility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* will return theme class in various instances of the page
*/
class ThemeClass extends ContentObjectRenderer
{
/**
* #var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
*/
public $cObj;
/**
* Returns theme class according to daylight
*
* #var string $content
* #var array $config
* #return string
**/
protected function class($content, array $config = null)
{
// init
$sunRise = $this->cObj->stdWrapValue('sunRise', $config, null);
$sunSet = $this->cObj->stdWrapValue('sunSet', $config, null);
$format = 'h:i a';
$now = date_create_from_format($format, Date($format));
$open = date_create_from_format($format, $sunRise);
$close = date_create_from_format($format, $sunSet);
return $theme = ($now > $open && $now < $close) ? 'light' : 'dark' ;
}
/**
* Returns body tag according to theme
*
* #var string $content
* #var array $config
* #return string
**/
public function bodyTag($content, array $config = null)
{
$theme = $this->class($content, $config);
return '<body class="'.$theme.'">';
}
/**
* Returns css according to theme
*
* #var string $content
* #var array $config
* #return string
**/
public function themeCss($content, array $config = null)
{
$theme = $this->class($content, $config);
return 'EXT:extension/Resources/Public/Css/_'.$theme.'.css">';
}
}
I use the theme overrideing the bodytag like this:
page.bodyTag >
page.bodyTagCObject = USER
page.bodyTagCObject {
userFunc = Vendor\Extension\Utility\ThemeClass->bodyTag
sunRise = {$extension.config.daylight.sunrise}
sunSet = {$extension.config.daylight.sunset}
}
I expected that something similar would work here :
page.includeCSS {
theme.cObject = USER
theme.cObject {
userFunc = Vendor\Extension\Utility\ThemeClass->themeCss
sunRise = {$extension.config.daylight.sunrise}
sunSet = {$extension.config.daylight.sunset}
}
}
but it doesn't work within includeCSS ... I tried a few 'side-steps' of this but cannot get anything to work ...
You could register a USER or USER_INT somewhere in the page object and in you user function get the AssetCollector to register CSS.
I ran into a problem. When I upload a file (image) as visual swatch attribute in Magento 2.2.2, nothing happens I see an empty field (I attached a screenshot)
Access log show a POST request with response code 200, and I checked the directory pub/media/attribute/swatch/swatch_image/30x20/0/1 the file is there.
You have to need change of core file other wise override to it
Path : vendor/magento/module-swatches/Block/Adminhtml/Attribute/Edit/OptionsVisual.php
public function getJsonConfig()
* Parse swatch labels for template
*
* #codeCoverageIgnore
* #param null $swatchStoreValue //Remove
* #return string //Remove
* #param null|array $swatchStoreValue //Add
* #return null|array //Add
*/
protected function reformatSwatchLabels($swatchStoreValue = null)
{
if ($swatchStoreValue === null) {
return;
}
$newSwatch = ''; // Remove
$newSwatch = []; //Add
foreach ($swatchStoreValue as $key => $value) {
if ($value[0] == '#') {
$newSwatch[$key] = 'background: '.$value;
In a custom extbase extension, I need to call a show action, passing it another value than uid (this is a continuation of Use other than primary key as RealURL id_field).
As the value "other than uid" is not resolved, it results in the exception http://wiki.typo3.org/Exception/CMS/1297759968
EDIT: here's the current working (but ugly) Controller code:
/**
* ItemController
*/
class ItemController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* itemRepository
*
* #var \STUBR\Weiterbildung\Domain\Repository\ItemRepository
* #inject
*/
protected $itemRepository = NULL;
/**
* action list
*
* #return void
*/
public function listAction(\STUBR\Weiterbildung\Domain\Model\Item $item=null) {
if (!empty(\TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1'))){
$this->forward('show');
}
$items = $this->itemRepository->findAll();
$this->view->assign('items', $items);
}
/**
* action show
*
* #param \STUBR\Weiterbildung\Domain\Model\Item $item
* #return void
*/
public function showAction(\STUBR\Weiterbildung\Domain\Model\Item $item=null) {
$tx_weiterbildung_pi1_get = \TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1');
if (!empty($tx_weiterbildung_pi1_get)){
$dfid = intval($tx_weiterbildung_pi1_get['durchfuehrungId']);
}
$items = $this->itemRepository->findByDurchfuehrungId($dfid);
// helpful:
//\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($item);
$this->view->assign('item', $items[0]);
}
}
PS here are the 5 lines that could be used in the Repository if the magical method wouldn't work:
//public function findByDurchfuehrungId($dfid) {
// $query = $this->createQuery();
// $query->matching($query->equals('durchfuehrungId', $dfid));
// return $query->execute();
//}
Yes, when you're using actions with model binding in param it will always look for object by field defined as ID - in our case it's always uid, but remember, that you do not need to bind model automatically, as you can retrive it yourself.
Most probably you remember, that some time ago I advised to use <f:link.page additionalParams="{originalUid:myObj.originalUid}"Click here</f:link.page> instead of <f:link.action...>
In that case your show action would look like this:
public function showAction() {
$item = $this->itemRepository->findByOriginalUid(intval(GeneralUtility::_GET('originalUid')));
$this->view->assign('item', $item);
}
Where findByOriginalUid should work magically without declaration, but even if it doesn't it's just matter of 5 lines in the repo ;)
Other sample
According to the code you pasted I'd use it rather like this, in this case listAction gets a role of dispatcher for whole plugin:
public function listAction() {
// access get param in array
$tx_weiterbildung_pi1_get = \TYPO3\CMS\Core\Utility\GeneralUtility::_GET('tx_weiterbildung_pi1');
$dfId = intval($tx_weiterbildung_pi1_get['durchfuehrungId']);
if ($dfId > 0) { // tx_weiterbildung_pi1 exists and is positive, that means you want to go to showAction
$item = $this->itemRepository->findByDurchfuehrungId($dfId);
if (!is_null($item)) { // Forward to showAction with found $item
$this->forward('show', null, null, array('item' => $item));
}else { // Or redirect to the view URL after excluding single item ID from GET params
$this->redirectToUri($this->uriBuilder->setArgumentsToBeExcludedFromQueryString(array('tx_weiterbildung_pi1'))->build());
}
}
// No `tx_weiterbildung_pi1` param, so it should be displayed as a listAction
$items = $this->itemRepository->findAll();
$this->view->assign('items', $items);
}
/**
* #param \STUBR\Weiterbildung\Domain\Model\Item $item
*/
public function showAction(\STUBR\Weiterbildung\Domain\Model\Item $item = null) {
$this->view->assign('item', $item);
}
Your finder should also getFirst() object if possible:
public function findByDurchfuehrungId($DfId) {
$query = $this->createQuery();
$query->matching($query->equals('durchfuehrungId', $DfId));
return $query->execute()->getFirst();
}
There is a multiselect element in form. It's needed to validate how many items are selected in it (min and max count).
The trouble is that when the element can have multiple values, then each value is validated separately.
I tried to set isArray to false to validate the value with my custom validator ArraySize, but new problem appeared: the whole array-value is passed to InArray validator and the validation fails. So I had to turn it off by setting registerInArrayValidator to false.
Now I can validate the value for number of selected values but can not validate for their correspondence to provided options.
Is there a way to solve the problem without creating one more custom validator?
Note: I have assume this is Zend 1.
The only way I could see to do this was to extend Multiselect and use a custom isValid. This way you can see the full array of values and not just one value at a time.
Below is my custom Multiselect class
<?php
/**
* My_MinMaxMultiselect
*/
class My_MinMaxMultiselect extends Zend_Form_Element_Multiselect
{
/**
* Validate element contains the correct number of
* selected items. Check value against minValue/maxValue
*
* #param mixed $value
* #param mixed $context
* #return boolean
*/
public function isValid($value, $context = null)
{
// Call Parent first to cause chain and setValue
$parentValid = parent::isValid($value, $context);
$valid = true;
if ((('' === $value) || (null === $value))
&& !$this->isRequired()
&& $this->getAllowEmpty()
) {
return $valid;
}
// Get All Values
$minValue = $this->getMinValue();
$maxValue = $this->getMaxValue();
$count = 0;
if (is_array($value)) {
$count = count($value);
}
if ($minValue && $count < $minValue) {
$valid = false;
$this->addError('The number of selected items must be greater than or equal to ' . $minValue);
}
if ($maxValue && $count > $maxValue) {
$valid = false;
$this->addError('The number of selected items must be less than or equal to ' . $maxValue);
}
return ($parentValid && $valid);
}
/**
* Get the Minimum number of selected values
*
* #access public
* #return int
*/
public function getMinValue()
{
return $this->getAttrib('min_value');
}
/**
* Get the Maximum number of selected values
*
* #access public
* #return int
*/
public function getMaxValue()
{
return $this->getAttrib('max_value');
}
/**
* Set the Minimum number of selected Values
*
* #param int $minValue
* #return $this
* #throws Bad_Exception
* #throws Zend_Form_Exception
*/
public function setMinValue($minValue)
{
if (is_int($minValue)) {
if ($minValue > 0) {
$this->setAttrib('min_value', $minValue);
}
return $this;
} else {
throw new Bad_Exception ('Invalid value supplied to setMinValue');
}
}
/**
* Set the Maximum number of selected values
*
* #param int $maxValue
* #return $this
* #throws Bad_Exception
* #throws Zend_Form_Exception
*/
public function setMaxValue($maxValue)
{
if (is_int($maxValue)) {
if ($maxValue > 0) {
$this->setAttrib('max_value', $maxValue);
}
return $this;
} else {
throw new Bad_Exception ('Invalid value supplied to setMaxValue');
}
}
/**
* Retrieve error messages and perform translation and value substitution.
* Overridden to avoid errors from above being output once per value
*
* #return array
*/
protected function _getErrorMessages()
{
$translator = $this->getTranslator();
$messages = $this->getErrorMessages();
$value = $this->getValue();
foreach ($messages as $key => $message) {
if (null !== $translator) {
$message = $translator->translate($message);
}
if (($this->isArray() || is_array($value))
&& !empty($value)
) {
$aggregateMessages = array();
foreach ($value as $val) {
$aggregateMessages[] = str_replace('%value%', $val, $message);
}
// Add additional array unique to avoid the same error for all values
$messages[$key] = implode($this->getErrorMessageSeparator(), array_unique($aggregateMessages));
} else {
$messages[$key] = str_replace('%value%', $value, $message);
}
}
return $messages;
}
}
To use this in the form where the User must select exactly 3 options:
$favouriteSports = new MinMaxMultiselect('favourite_sports');
$favouriteSports
->addMultiOptions(array(
'Football',
'Cricket',
'Golf',
'Squash',
'Rugby'
))
->setRequired()
->setLabel('Favourite Sports')
->setMinValue(3)
->setMaxValue(3);
While it's nice when you can squeeze by without needing to write a custom validator, there is nothing wrong with writing one when you need to do something slightly out of the ordinary.
This sounds like one of those cases.
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 11 years ago.
This is basically the "Am i doing it right?" question.
I have an idea how i can transparently switch views for default/mobile version/admin areas at run time. And I would like to know what pros and cons you see in this approach.
Main requirements are:
switch entire application with
little to no coding
integrate into zend MVC workflow,
not overwrite it
fallback to default
preserve standard functionality
controllers shouldn't be aware of
changes
Here is my pseudohelper
class Xrks_Controller_Action_Helper_VrExtension extends Zend_Controller_Action_Helper_Abstract
{
public function postDispatch()
{
if(!$this->_shouldRender()) {
return; //just skip
}
try {
$vr = $this->_getViewRenderer();
$backupView = clone $vr->view;
$this->_setBasePaths(); //set base path(s) through ViewRenderer::initView($path)
$oldSpecArray = $this->_setVrPathSpecs(); //set VR view script path specs
$vr->render();
$vr->setNoRender(true); //disable renderer
} catch(Zend_View_Exception $e) { //fallback to default viewscripts if view script file not found
$vr->setView($backupView); //restore view on error
} catch(Exception $e) {
$vr->setView($backupView); //restore view on error
$this->_setVrPathSpecs($oldSpecArray); //restore script path spec
throw $e;
}
$this->_setVrPathSpecs($oldSpecArray);//restore script path spec
}
/**
* Same functionality as ViewRenderer helper _shouldRender method
* #return boolean
*/
protected function _shouldRender();
/**
* #return Zend_Controller_Action_Helper_ViewRenderer
*/
protected function _getViewRenderer();
/**
* Sets viewRenderer path specifications
*
* #param array $spec if NULL uses $this->_viewRendererPathSpecs
* #return array old path spec (0 => pathSpec, 1 => pathNoControllerSpec)
*/
protected function _setVrPathSpecs(array $spec = NULL);
}
How exactly helper should be configured is not important and that part skipped
Here is example how it supposed to work:
$this->_setBasePaths(); sets view base paths to application/views/default/ and application/views/admin/
$this->_setVrPathSpecs(); set path specification to ':module/:controller/:action.:suffix'
so for foo-baz-bar it will search at
1. application/views/admin/scripts/foo/baz/bar.phtml
2. application/views/default/scripts/foo/baz/bar.phtml
if view script not found fall back to default ViewRenderer:
3. application/modules/foo/views/scripts/baz/bar.phtml
Ask questions if I missed something
Upd: After some research i decided to use action helper to autoregister view scriptPaths based on specification for inflector and specified variables. I also modified partial helpers to register scriptPaths if partial from other module requested.
This is crude but working version of action helper:
class Xrks_Controller_Action_Helper_ViewRendererPathstack extends Zend_Controller_Action_Helper_Abstract
{
const PATH_APPEND = 'append';
const PATH_PREPEND = 'prepend';
protected $_enabled = FALSE;
protected $_viewScriptPaths = array();
/**
* By default following vars available: baseDir, area, theme, module
* #var string
*/
protected $_viewScriptPathSpec = ':baseDir/:area/:module';
protected $_defaults = array(
'area' => 'frontend',
'theme' => 'default',
);
protected $_vars = array();
protected $_inflector;
protected $_viewRenderer;
public function __construct($baseDir = NULL)
{
if($baseDir == NULL) {
$baseDir = APPLICATION_PATH . DS . 'views';
}
$this->setDefaultVar('baseDir', $baseDir);
$this->addPath(array());
}
/**
* Enter description here ...
* #return Zend_Controller_Action_Helper_ViewRenderer
*/
protected function _getViewRenderer()
{
if(!$this->_viewRenderer) {
$this->_viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
}
return $this->_viewRenderer;
}
/**
* Should the ViewRenderer render a view script?
*
* #return boolean
*/
protected function _shouldRender()
{
$vR = $this->_getViewRenderer();
return (!$this->getFrontController()->getParam('noViewRenderer')
&& !$vR->getNeverRender()
&& !$vR->getNoRender()
&& (null !== $vR->getActionController())
&& $vR->getRequest()->isDispatched()
&& !$vR->getResponse()->isRedirect()
);
}
public function generatePaths(array $vars = array())
{
$this->_registerVarsWithInflector();
$vars = array_merge($this->_defaults, $this->_vars, $vars);
$inflector = $this->getInflector();
$generatedPaths = array();
foreach($this->_viewScriptPaths as $path) {
$pathVars = array_merge($vars, $path);
$generatedPaths[] = $inflector->filter($pathVars);
}
return array_reverse(array_unique(array_reverse($generatedPaths)));//last occurence more important than first
// array('test', 'test2', 'test') => array('test2', 'test')
// #todo rethink this code piece later. must be better solution
}
protected function _registerVarsWithInflector()
{
$vars = array_merge($this->_defaults, $this->_vars);
$inflector = $this->getInflector();
$unregistered = array_keys(array_diff_key($vars, $inflector->getRules()));
sort($unregistered, SORT_DESC);//more specific first (moduleDir prior to module key)
foreach($unregistered as $var) {
$inflector->addFilterRule($var, array('Word_CamelCaseToDash', 'StringToLower'));
}
}
protected function _viewAddScriptPaths(Zend_View_Abstract $view, $paths)
{
foreach ($paths as $path) {
$view->addScriptPath($path);
}
}
/**
* Get inflector
*
* #return Zend_Filter_Inflector
*/
public function getInflector()
{
if (null === $this->_inflector) {
$this->_inflector = new Zend_Filter_Inflector();
$this->_inflector->setThrowTargetExceptionsOn(true);
//setup default rules
$this->_inflector->addRules(array(
':baseDir' => array(),
))
->setTargetReference($this->_viewScriptPathSpec);
}
return $this->_inflector;
}
/**
*
* #return array
*/
public function getPaths()
{
return $this->_basePaths;
}
public function getEnabled()
{
return $this->_enabled;
}
public function setEnabled($flag = TRUE)
{
$this->_enabled = (bool)$flag;
return $this;
}
/**
*
* #todo add check for $pathVars keys and values validity
* #param array $pathVars associative array
* #param string $placement either append or prepend
* #return Xrks_Controller_Action_Helper_ViewRendererPathstack
*/
public function addPath(array $pathVars, $placement = self::PATH_APPEND)
{
if($placement == self::PATH_PREPEND) {
array_unshift($this->_viewScriptPaths, $pathVars);
} else {
$this->_viewScriptPaths[] = $pathVars;
}
return $this;
}
/**
*
* #param array|Zend_Config $paths
* #param string $placement either append or prepend
* #return Xrks_Controller_Action_Helper_ViewRendererPathstack
* #throws Xrks_Exception
*/
public function addPaths($paths, $placement = self::PATH_APPEND)
{
if($paths instanceof Zend_Config) {
$paths = $paths->toArray();
} elseif (!is_array($paths)) {
throw new Xrks_Exception('$paths should be either array or instance of Zend_Config');
}
if($placement == self::PATH_PREPEND) {
$paths = array_reverse($paths);
}
foreach($paths as $path) {
$this->addPath((array)$path, $placement);
}
return $this;
}
/**
*
* #param array $pathVars associative array
* #return Xrks_Controller_Action_Helper_ViewRendererPathstack
*/
public function setPath(array $pathVars)
{
$this->_basePaths = array();
$this->addPath($pathVars);
return $this;
}
/**
*
* #param array|Zend_Config $paths
* #return Xrks_Controller_Action_Helper_ViewRendererPathstack
* #throws Xrks_Exception
*/
public function setPaths($paths)
{
$this->_basePaths = array();
$this->addPaths($paths);
return $this;
}
/**
*
* #param string $varName
* #return string |NULL
*/
public function getDefaultVar($varName)
{
if(key_exists($varName, $this->_defaults)) {
return $this->_defaults[$varName];
}
return NULL;
}
/**
* #param string $varName
* #param string $value
* #return Xrks_Controller_Action_Helper_ViewRendererPathstack Provides fluent interface
*/
public function setDefaultVar($varName, $value)
{
$this->_defaults[$varName] = (string)$value;
return $this;
}
/**
*
* #param string $name
* #return string |NULL
*/
public function getVar($name, $defaults = false)
{
if(key_exists($name, $this->_vars)) {
return $this->_vars[$name];
}
return $defaults ? $this->getDefaultVar($name) : NULL;
}
/**
* #param string $varName
* #param string $value
* #return Xrks_Controller_Action_Helper_ViewRendererPathstack Provides fluent interface
*/
public function setVar($varName, $value)
{
$this->_vars[$varName] = $value;
return $this;
}
public function unsetVar($name)
{
if(key_exists($name, $this->_vars)) {
unset($this->_vars[$name]);
}
return $this;
}
public function postDispatch()
{
if(!$this->getEnabled() || !$this->_shouldRender()) {
return; //just skip
}
try {
$vr = $this->_getViewRenderer();
$this->setVar('module', $vr->getModule());
$paths = $this->generatePaths();
$this->_viewAddScriptPaths($vr->view, $paths);
if(Zend_Registry::isRegistered('Zend_Log')) {
Zend_Registry::get('Zend_Log')
->log($paths, Zend_Log::DEBUG);
}
} catch(Exception $e) {
if(Zend_Registry::isRegistered('Zend_Log')) {
Zend_Registry::get('Zend_Log')
->log($e, Zend_Log::WARN);
}
throw $e;
}
}
}
The way I usually handle this:
I register a Layout Plugin, extending Zend_Layout_Controller_Plugin_Layout
I use the preDispatch hook to determine what module, controller, action I am in
I switch between layouts and views depending on the context
For me, that's by far the easiest method.
GJ