Typo3: verhindern, dass eine Übersetzung beim kopieren auf hidden gesetzt wird

15. Dezember 2015

Geht erstaunlicherweise mit TypoScript sehr einfach, und man kann auch verhindern, dass der “[Translate to english]” Text am Anfang der Überschrift eingefügt wird:

  1. //for pages
  2. TCEMAIN.table.pages {
  3. disablePrependAtCopy = 1 //the [Translate to XXX] text
  4. disableHideAtCopy = 1 //hidden
  5. }
  6. //for content elements
  7. TCEMAIN.table.tt_content {
  8. disablePrependAtCopy = 1
  9. disableHideAtCopy = 1
  10. }

Habe ich hier gefunden.

typo3 Seitenansicht: Erscheinungsbild eines Inhaltselements beeinflussen

15. Dezember 2015

Ein Kunde wollte in der Seitenansicht direkt sehen, welche Option er bei “Einrückung und Rahmen” gewählt hat. Eigentlich wollte ich das gerne in den grauen Balken eines Inhaltselements schreiben, aber den kann man offenbar nicht so einfach manipulieren. Was man aber beeinflussen kann mit einem Hook sind Titel und Text drunter! Im ext_localconf.php füge ich folgendes hinzu:

  1. $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']['section_frame_name'] = 'Ophi\\OphiSomething\\Hook\\CustomPageLayoutView';

Und in Ophi/OphiSomething/Classes/Hook gibt es dann die Hook Datei, die so aussieht:

  1. namespace Ophi\OphiSomething\Hook;
  2. use TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface, TYPO3\CMS\Backend\View\PageLayoutView;
  3. class CustomPageLayoutView implements PageLayoutViewDrawItemHookInterface {
  4. /**
  5. * Preprocesses the preview rendering of a content element.
  6. *
  7. * @param PageLayoutView $parentObject Calling parent object
  8. * @param boolean $drawItem Whether to draw the item using the default functionalities
  9. * @param string $headerContent Header content
  10. * @param string $itemContent Item content
  11. * @param array $row Record row of tt_content
  12. * @return void
  13. */
  14. public function preProcess(\TYPO3\CMS\Backend\View\PageLayoutView &$parentObject, &$drawItem, &$headerContent, &$itemContent, array &$row) {
  15. $headerContent = str_replace("</a>", " (Section Frame: " . ($row['section_frame']) . ")</a>", $headerContent);
  16. }
  17. }

Und das Ergebnis sieht in der Seitenansicht dann so aus:
sectionframe
Dass ich das relativ schnell herausgefunden habe, verdanke ich diesem Stackoverflow Beitrag und dieser Erklärung hier

Typo3 extbase: Services einbinden

11. Dezember 2015

Mir hat nie wer ausführlich erklärt, wie man z.B. in einem Service auf einen anderen Service zugreift. Nun hab ichs glaub ich endlich kapiert. Das Problem: ich habe einen ViewHelper, der einen Service (calcService) aufruft und in dem Service will ich wieder einen anderen Service benutzen, nämlich den AuthenticationService, um Infos über den eingeloggten User zu bekommen. Das Problem: ich bekam immer folgende Fehlermeldung:

  1. Fatal error: Call to a member function getUser() on a non-object

Das Problem ist, dass man Services und andere Klassen offenbar nicht statisch einbauen sondern injecten oder über den ObjectManager einfügen sollte. In meinem Fall hatte ich das im Service versucht, aber nicht daran gedacht, dass ich schon im ViewHelper den ersten Service falsch ausgerufen hatte.
FALSCH:

  1. use Ophi\OphiSomething\Service\CalcService;
  2. class CalcSomethingViewHelper extends AbstractViewHelper {
  3. public function render($str) {
  4. return CalcService::doSomething($str);
  5. }
  6. }

RICHTIG:

  1. use Ophi\OphiSomething\Service\CalcService;
  2. class CalcSomethingViewHelper extends AbstractViewHelper {
  3. /**
  4. * @var \Ophi\OphiSomething\Service\CalcService
  5. * @inject
  6. */
  7. private $calcService;
  8. public function render($str) {
  9. return $this->calcService($str);
  10. }
  11. }

…muss man halt auch erstmal wissen.

Magento: Produktseiten werfen beim Aufrufen manchmal einen 404er error

09. Dezember 2015

Ein merkwürdiges Problem, das mir nun schon bei 2 Magento installationen (Version 1.9.x.x) begegnet ist: ab und zu passiert es, dass Produktseiten nach dem Login nicht mehr erreichbar sind und stattdessen ein 404 error angezeigt wird. Im log (var/log/exception.log) steht dann

  1. Next exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'xxxxx-xxx' for key 'UNQ_REPORT_VIEWED_PRODUCT_INDEX_CUSTOMER_ID_PRODUCT_ID'' in /path/to/shop/lib/Zend/Db/Statement/Pdo.php:234

Dieser Bugreport sieht ganz nach dem selben Problem aus, es scheint als wäre das ein bekanntes Problem von Magento < 2. Der Bugfix steht dann praktischerweise auch gleich mit drin, ich empfehle die Lösung von andrr ganz unten, und zwar dass man in das Magento core eingreift und folgende Datei bearbeitet: app/code/core/Mage/Reports/Model/Resource/Product/Index/Abstract.php in der save() Methode ergänzt man folgendes:

  1. unset($data[$this->getIdFieldName()]);
  2. //start fix
  3. if(Mage::getSingleton(‘customer/session’)->isLoggedIn()){
  4. $this->updateCustomerFromVisitorByProductId($object);
  5. }
  6. //end fix
  7. $matchFields = array(‘product_id’, ‘store_id’);

Und fügt der Klasse die Methode updateCustomerFromVisitorByProductId hinzu:

  1. public function updateCustomerFromVisitorByProductId(Mage_Reports_Model_Product_Index_Abstract $object)
  2. {
  3. /**
  4. * Do nothing if customer not logged in
  5. */
  6. if (!$object->getCustomerId() || !$object->getVisitorId() || !$object->getProductId()) {
  7. return $this;
  8. }
  9. $adapter = $this->_getWriteAdapter();
  10. $select = $adapter->select()
  11. ->from($this->getMainTable())
  12. ->where('visitor_id = ?', $object->getVisitorId())
  13. ->where('product_id = ?', $object->getProductId());
  14. $rowSet = $select->query()->fetchAll();
  15. foreach ($rowSet as $row) {
  16. $select = $adapter->select()
  17. ->from($this->getMainTable())
  18. ->where('customer_id = ?', $object->getCustomerId())
  19. ->where('product_id = ?', $row['product_id']);
  20. $idx = $adapter->fetchRow($select);
  21. if ($idx) {
  22. /* If we are here it means that we have two rows: one with known customer, but second just visitor is set
  23. * One row should be updated with customer_id, second should be deleted
  24. */
  25. $adapter->delete($this->getMainTable(), array('index_id = ?' => $row['index_id']));
  26. $where = array('index_id = ?' => $idx['index_id']);
  27. $data = array(
  28. 'visitor_id' => $object->getVisitorId(),
  29. 'store_id' => $object->getStoreId(),
  30. 'added_at' => Varien_Date::now(),
  31. );
  32. } else {
  33. $where = array('index_id = ?' => $row['index_id']);
  34. $data = array(
  35. 'customer_id' => $object->getCustomerId(),
  36. 'store_id' => $object->getStoreId(),
  37. 'added_at' => Varien_Date::now()
  38. );
  39. }
  40. $adapter->update($this->getMainTable(), $data, $where);
  41. }
  42. return $this;
  43. }

Bei mir hats damit dann funktioniert!

typo3: scheduler task mit zusätzlichen Felder (additionalFields)

04. Dezember 2015

Wie ein Scheduler Task mit extbase geht, weiß ich inzwischen, aber ich habe die letzten Stunden damit zugebracht, wie man bei so einem extbase CommandController dann zusätzliche Felder hinzufügen kann. Ob es geht? Ich weiß es nicht. Gefunden habe ich jedenfalls nichts. Aber es geht anders, indem man nämlich keinen Extbase CommandController macht (kann aber nach wie vor so benannt werden und Teil der Extbase Struktur sein). Das meiste habe ich mir von dieser guten Anleitung zusammengereimt.

Ich hatte bereits einen CommandController (extensionname/Classes/Command/SomethingCommandController.php), dieser muss nur ein wenig umgeschrieben werden:

  • die auszuführende Funktion muss nun execute() heißen und nicht irgendwasCommand()
  • ich hatte eine __construct Funktion, was anscheinend problematisch ist. google rät, parent::__construct() dort aufzurufen, aber das hat bei mir auch nicht geholfen. Ich habe die __construct einfach komplett gestrichen und den Code von dort in execute() reinkopiert

Aber beginnen wir von vorne, zunächst wird der Controller in der ext_localconf.php gesetzt, und zwar etwas anders als bei den sonstigen Extbase Tasks. Nehmen wir mal an mein Package heißt ophi und meine Extension ophi_something

  1. $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['Ophi\OphiSomething\Command\SomethingCommandController'] = array(
  2. 'extension' => $_EXTKEY,
  3. 'title' => 'Something Task',
  4. 'description' => 'Do something really important with additional fields',
  5. 'additionalFields' => 'Ophi\OphiSomething\Command\SomethingCommandControllerAdditionalFieldProvider'
  6. );

Hier sieht man dann auch schon, wie auf die AdditionalFields verwiesen wird: es gibt hierfür eine eigene Klasse, die die zusätzlichen Felder definiert, validiert und speichert. Das alles passiert in der SomethingCommandControllerAdditionalFieldProvider Klasse. Ob die wirklich gleich wie der CommandController heißen muss weiß ich nicht, aber ich wollt’s nicht riskieren. Die Klasse sieht jedenfalls so aus:

  1. namespace Ophi\OphiSomething\Command;
  2. class SomethingCommandControllerAdditionalFieldProvider implements \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface {
  3. protected $important_something_var;
  4. public function getAdditionalFields(array &$taskInfo, $task, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $parentObject) {
  5. // Initialize selected fields
  6. if (!isset($taskInfo['important_something_var'])) {
  7. $taskInfo['important_something_var'] = 'default value';
  8. if ($parentObject->CMD === 'edit') {
  9. $taskInfo['important_something_var'] = $task->important_something_var;
  10. }
  11. }
  12. $fieldName = 'tx_scheduler[important_something_var]';
  13. $fieldId = 'important_something_var';
  14. $fieldValue = $taskInfo['important_something_var'];
  15. $fieldHtml = '<input type="text" name="' . $fieldName . '" id="' . $fieldId . '" value="' . htmlspecialchars($fieldValue) . '" />';
  16. $additionalFields[$fieldId] = array(
  17. 'code' => $fieldHtml,
  18. 'label' => 'A very important value',
  19. 'cshKey' => '_MOD_tools_txschedulerM1',
  20. 'cshLabel' => $fieldId
  21. );
  22. return $additionalFields;
  23. }
  24. public function validateAdditionalFields(array &$submittedData, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $parentObject) {
  25. $string = $submittedData['important_something_var'];
  26. if(trim($string) == ''){
  27. $parentObject->addMessage($GLOBALS['LANG']->sL('Value must not be empty'), \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
  28. return FALSE;
  29. }
  30. return true;
  31. }
  32. public function saveAdditionalFields(array $submittedData, \TYPO3\CMS\Scheduler\Task\AbstractTask $task) {
  33. $task->important_something_var = $submittedData['important_something_var'];
  34. }
  35. }

In validateAdditionalFields kann man beliebige Validierungen einfügen, in meinem Fall darf der Wert einfach nicht leer sein. Was cshKey ist, weiß ich nach wie vor nicht, ich habe keine Ahnung, ob das so heißen muss oder nicht. Last but not least dann noch der SomethingCommandController:

  1. namespace Ophi\OphiSomething\Command;
  2. class SomethingCommandController extends \TYPO3\CMS\Scheduler\Task\AbstractTask{
  3. public function execute(){
  4. //the additional field value
  5. $theImportantValue = $this->important_something_var;
  6. //...
  7. return true;
  8. }
  9. }

Und das wars. Wichtig: beim erstellen des neuen Tasks nicht mehr einen ExtbaseController auswählen, sondern das, was man in der ext_localconf als title angegeben hat wird irgendwo in der Liste separat auftauchen!