-
-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RTM] Extended picker #755
Conversation
8dbb75b
to
5d1443a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good at first sight! Nice work! I think there are quite some adjustments needed, though 😄
General thoughts:
- Lots of repetition, I think even though the DC_* classes are shit, we should not continue that pattern.
- What about the old
page.php
andfile.php
picker entry points? Can we map them BC to the new picker? Or is this only forfilePicker
andpagePicker
? - Did you think about maybe unifying that whole picker stuff? I mean right now we have
pageTree
,filePicker
and now some picker wizard callbacks. What if this just becomes the inputTypepicker
and all you need is configured through widget config? We could just map the oldpageTree
andfileTree
configuration to the new picker and drop the old widgets maybe?
src/Menu/PickerMenuBuilder.php
Outdated
['uri' => $this->route('contao_backend', 'files', $request)] | ||
); | ||
|
||
$item->setLinkAttribute('class', 'filemounts'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be handled by the renderer. That's what the interface method setCurrent()
is for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this comment relates to line 177, doesn't it? Because setCurrent()
will not set the link attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this comment must have been moved.
@@ -1579,7 +1579,11 @@ public function tableImportWizard() | |||
*/ | |||
public function pagePicker(DataContainer $dc) | |||
{ | |||
return ' <a href="' . (($dc->value == '' || strpos($dc->value, '{{link_url::') !== false) ? 'contao/page' : 'contao/file') . '?do=' . Input::get('do') . '&table=' . $dc->table . '&field=' . $dc->field . '&value=' . rawurlencode(str_replace(array('{{link_url::', '}}'), '', $dc->value)) . '&switch=1' . '" title="' . StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['pagepicker']) . '" onclick="Backend.getScrollOffset();Backend.openModalSelector({\'width\':768,\'title\':\'' . StringUtil::specialchars(str_replace("'", "\\'", $GLOBALS['TL_DCA'][$dc->table]['fields'][$dc->field]['label'][0])) . '\',\'url\':this.href,\'id\':\'' . $dc->field . '\',\'tag\':\'ctrl_'. $dc->field . ((Input::get('act') == 'editAll') ? '_' . $dc->id : '') . '\',\'self\':this});return false">' . Image::getHtml('pickpage.svg', $GLOBALS['TL_LANG']['MSC']['pagepicker']) . '</a>'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like that I would need to write this for every extension. I always hated that. Is there maybe a better solution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still asking here. This seems pretty universal now to me. Why not add an 'eval' => ['picker' => true']
setting here to ease it for devs?
@@ -178,6 +178,26 @@ public function __construct($strTable) | |||
$this->arrValidFileTypes = \StringUtil::trimsplit(',', strtolower($GLOBALS['TL_DCA'][$this->strTable]['config']['validFileTypes'])); | |||
} | |||
|
|||
// Configure the picker |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What of this is specific to DC_Folder
only? Looks like something that should be part of DataContainer
? I think DataContainer
should handle this in a general way and the drivers extending it should override what they want to do differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have checked it and it does not really make sense to move any code into the parent class. Especially as we would have to add another abstract method setPickerValue()
, which will break compatibility with all existing DCs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Obviously I would not make it abstract
. I'd just add the "normal" case to DataContainer
and override the stuff that differs in the child tables :-)
$strClass = ' picker unselectable'; | ||
$strPicker .= ' id="tl_select"'; | ||
|
||
if (isset($GLOBALS['TL_DCA'][$this->strTable]['config']['picker']['insertTag'])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like that it's limited to insert tags. I agree that most of the times you would want to have some placeholder that is dynamically replaced by a value but it could be anything, it doesn't have to be an insert tag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not limited to insert tags; the picker will return any given value.
But the JavaScript additionally supports the data-inserttag
and data-callback
attributes; the first one will wrap the given value in an insert tag and the second one will trigger an Ajax callback. Both are optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.
@@ -2692,12 +2740,28 @@ protected function generateTree($path, $intMargin, $mount=false, $blnProtected=t | |||
{ | |||
$return .= ' <a href="'.$this->addToUrl('&act=move&mode=2&pid='.$currentEncoded).'" title="'.\StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['tl_files']['uploadFF'], $currentEncoded)).'">'.\Image::getHtml('new.svg', $GLOBALS['TL_LANG'][$this->strTable]['move'][0]).'</a>'; | |||
} | |||
|
|||
if ($this->strPickerField) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know, a lot of code is already duplicated and it's shit anyway but does that mean we have to add even more duplicate lines? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked this, too, but there are too many small differences to extract the code into a parent method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, let me check again once everything is implemented. There might be some optimization potential after all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have moved most of the code to the parent class now.
$this->arrPickerValue = $varValue; | ||
|
||
// TinyMCE will pass the path instead of the ID | ||
if (strpos($varValue[0], \Config::get('uploadPath') . '/') === 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This smells. If something needs a special handling this is always an indicator that it is even wrong or third party bundles might need special handling to = needs extension point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this is not new code. We are using this already:
https://github.com/contao/core-bundle/blob/master/src/Resources/contao/widgets/FileSelector.php#L625
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Still ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know what you mean. :)
src/Menu/PickerMenuBuilder.php
Outdated
* | ||
* @throws \RuntimeException | ||
*/ | ||
public function createMenu(BackendUser $user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think passing the user is not needed at all. The ones that want to limit the menu to some permissions should check for themselves, meaning the event listeners should listen to the event and inject the security.token_storage
and check for permissions there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also they need to set the label as stated above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in ab0ebb3.
src/Menu/PickerMenuBuilder.php
Outdated
* | ||
* @param string $pagePickerLabel | ||
*/ | ||
public function setPagePickerLabel($pagePickerLabel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces state and has nothing to do with the picker menu builder. The file picker and page picker should both listen to the event and set their own label.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fully agree that the service must not be stateful.
However, I think it is ok if the service adds the two core navigation items. Do you really think we need a separate event listener for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the core should not behave differently than all the other listeners. It will also prevent from stuff like "the core can do this, you cannot in the listener unfortunately".
It's separation of concerns, easier to test, easier to remove etc. pp. I mean, create an AbstractDcaListener
or something that gets the url, label and css class from its child class. That way it's a breeze implementing FAQ and other regular DCA related listeners.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in ab0ebb3.
$strPicker .= ' data-inserttag="' . $GLOBALS['TL_DCA'][$this->strTable]['config']['picker']['insertTag'] . '"'; | ||
} | ||
|
||
if (isset($GLOBALS['TL_DCA'][$this->strTable]['config']['picker']['callback'])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is data-callback
? Is this maybe missing in this PR? Also, I suppose it's a JS callback, can we maybe name it as such? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the data-callback
attribute we are using already (since Contao 4).
https://github.com/contao/core-bundle/blob/master/src/Resources/public/core.js#L952
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, didn't know that, cool stuff.
Thank you very much. It is great that we have the compatibility between the Contao's extensions. |
aa472d6
to
a759b51
Compare
a759b51
to
8a6e13c
Compare
Ok, I have completely reworked the implementation according to what @Toflar and I have discussed. It now uses tagged services that provide the picker information and a custom route that redirects depending on the picker value. Works great. 😄 Here's how to add a provider in your custom bundle: contao/news-bundle#10 |
src/Controller/BackendController.php
Outdated
* | ||
* @return Response | ||
* | ||
* @Route("/picker", name="contao_backend_picker") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me, this is definitely a rather internal route. I'd prefer to have something like _contao/picker
like we do for the cron.
@@ -1579,7 +1579,11 @@ public function tableImportWizard() | |||
*/ | |||
public function pagePicker(DataContainer $dc) | |||
{ | |||
return ' <a href="' . (($dc->value == '' || strpos($dc->value, '{{link_url::') !== false) ? 'contao/page' : 'contao/file') . '?do=' . Input::get('do') . '&table=' . $dc->table . '&field=' . $dc->field . '&value=' . rawurlencode(str_replace(array('{{link_url::', '}}'), '', $dc->value)) . '&switch=1' . '" title="' . StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['pagepicker']) . '" onclick="Backend.getScrollOffset();Backend.openModalSelector({\'width\':768,\'title\':\'' . StringUtil::specialchars(str_replace("'", "\\'", $GLOBALS['TL_DCA'][$dc->table]['fields'][$dc->field]['label'][0])) . '\',\'url\':this.href,\'id\':\'' . $dc->field . '\',\'tag\':\'ctrl_'. $dc->field . ((Input::get('act') == 'editAll') ? '_' . $dc->id : '') . '\',\'self\':this});return false">' . Image::getHtml('pickpage.svg', $GLOBALS['TL_LANG']['MSC']['pagepicker']) . '</a>'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still asking here. This seems pretty universal now to me. Why not add an 'eval' => ['picker' => true']
setting here to ease it for devs?
$strPicker = ''; | ||
|
||
// Add the picker attributes | ||
if ($this->strPickerField) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still a lot of repetition here. Can't we put this in its own method? :)
af43885
to
d3cab12
Compare
d3cab12
to
2dd757b
Compare
dbd99f3
to
d465dec
Compare
d465dec
to
c7e5aa3
Compare
@@ -270,7 +270,7 @@ | |||
'exclude' => true, | |||
'search' => true, | |||
'inputType' => 'text', | |||
'eval' => array('rgxp'=>'url', 'decodeEntities'=>true, 'maxlength'=>255, 'dcaPicker'=>true, 'fieldType'=>'radio', 'filesOnly'=>true, 'tl_class'=>'w50 wizard'), | |||
'eval' => array('rgxp'=>'url', 'decodeEntities'=>true, 'maxlength'=>255, 'dcaPicker'=>array(), 'fieldType'=>'radio', 'filesOnly'=>true, 'tl_class'=>'w50 wizard'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should remain 'dcaPicker'=>true
but optionally accepting an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@ausi We could add the following code here: $this->setPickerValue();
$strDriver = 'DC_' . $GLOBALS['TL_DCA'][$this->strPickerTable]['config']['dataContainer'];
$objDca = new $strDriver($this->strPickerTable);
$objDca->field = $this->strPickerField;
// Set the active record
if (isset($_GET['id']) && $this->Database->tableExists($this->strPickerTable))
{
/** @var Model $strModel */
$strModel = \Model::getClassFromTable($this->strPickerTable);
if (class_exists($strModel))
{
$objModel = $strModel::findByPk(\Input::get('id'));
if ($objModel !== null)
{
$objDca->activeRecord = $objModel;
}
}
}
// Call the load_callback
if (is_array($GLOBALS['TL_DCA'][$this->strPickerTable]['fields'][$this->strPickerField]['load_callback']))
{
foreach ($GLOBALS['TL_DCA'][$this->strPickerTable]['fields'][$this->strPickerField]['load_callback'] as $callback)
{
if (is_array($callback))
{
$this->import($callback[0]);
$this->arrPickerValue = $this->{$callback[0]}->{$callback[1]}($this->arrPickerValue, $objDca);
}
elseif (is_callable($callback))
{
$this->arrPickerValue = $callback($this->arrPickerValue, $objDca);
}
}
} Would this solve your problem? |
Nope, the ID is not missing. It is added by the <p><a href="' . ampersand(\System::getContainer()->get('router')->generate('contao_backend_picker', array('do'=>'page', 'target'=>$this->strTable.'.'.$this->strField, 'value'=>implode(',', $arrSet), 'id'=>$this->activeRecord->id, 'popup'=>1))) . '" class="tl_submit" id="pt_' . $this->strField . '">'.$GLOBALS['TL_LANG']['MSC']['changeSelection'].'</a></p>
(The changes have not been pushed yet.) |
But wouldn’t the |
Yes, you are right – as always. 😄 |
Well, you were only half way right. It However, your changes in line 484 now always add the ID, which is a different behavior than before. I don't think it will break anything, but we should review it carefully. |
if (!isset($GLOBALS['TL_DCA'][$this->strPickerTable]['fields'][$this->strPickerField])) | ||
{ | ||
throw new InternalServerErrorException('Target field "' . $this->strPickerTable . '.' . $this->strPickerField . '" does not exist.'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if
needs to be moved to the end of the method, because the DCA onload_callback
may add the field dynamically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in d86a642.
src/Resources/public/core.js
Outdated
@@ -941,20 +941,24 @@ var Backend = | |||
alert('Could not find the SimpleModal frame'); | |||
return; | |||
} | |||
ul = frm.document.getElementById(opt.id); | |||
ul = frm.document.getElementById('tl_select'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change broke an existing URL picker (similar to the old one from tl_content
) in my test. Without this change the picker works as expected, but with it the {{link_url::*}}
insert tag around the ID is missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, the core is not passing an id
attribute anymore, because you are supposed to pass a callback function instead. Therefore, tl_select
is kind of a convention.
Is it crucial that we support opt.id
here, too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it’s very likely that somebody copied the tl_content::pagePicker()
method to use it for an extension or in a project, this would include \'id\':\'' . $dc->field . '\',
too. Not supporting opt.id
is be a BC break I think.
How about using tl_select
as a fallback?
frm.document.getElementById(opt.id || 'tl_select')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's possible of course, but it does not really help. Do we want to support opt.id
in Contao 5 as well? Then we should encourage people to provide it. Or do we want to use tl_select
in Contao 5? Then we should add a deprecation notice if someone uses opt.id
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to support
opt.id
in Contao 5 as well?
I think it’s OK to deprecate it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed in 2b43a70.
Description ----------- As discussed in contao/contao#745 (comment) If no image size is selected or configured the image should not get modified by Contao and the original image should be used instead. An exception to this rule is if the image has an EXIF orientation tag, in this case the image gets rotated. Commits ------- f33ebafb Enable skipIfDimensionsMatch for empty configs 3903b17e CS fixes
This PR is the basis for an extended picker that allows to pick news, events etc. (see #7656).
It uses the KnpMenuBundle to generate the menu and dispatches the
contao.build_picker_menu
event so you can extend it (see http://symfony.com/doc/current/bundles/KnpMenuBundle/events.html).TODO