Drupal, hook_form_alter add field in node edit / create - forms

I have a node and I need to populate a field programmatically, so here is what I do :
$campaigns = $client->get_campaigns();
$tab_campaign = array(""=>"Dernière newsletter");
foreach ($campaigns->response as $camp){
$tab_campaign[$camp->CampaignID] = $camp->Name;
}
$form['field_last_newsletter'] = array(
'#type' => 'select',
'#required' => true,
'#options' => $tab_campaign,
'#title' => 'Choisir la dernière newsletter',
);
}
This work, I have my select field populated but when I select one and click on save nothing is saved, if I come back to the edit page the select have the default value, what I am doing wrong ?
Thanks.

I think you're looking for a allowed_values_function
setting for option fields. It is a perfect solution for fields with dynamic options.
First, you need to change the current field settings to use the function to set the allowed values.
To do this, modify the field settings in features (if used):
// Exported field_base: 'field_last_newsletter'
// my_module.features.field_base.inc
$field_bases['field_last_newsletter'] = array(
// ....
'settings' => array(
'allowed_values' => array(),
'allowed_values_function' => 'my_module_field_last_newsletter_allowed_values',
),
// ....
);
If you do not use features, you can make this change by executing the PHP code or using hook_update_N
/**
* Implements hook_update_N().
* Update the field_last_newsletter field settings to use callback for allowed_values.
*/
function my_module_update_N(&$sandbox) {
// get default status for field using machine name of field
$default_stats_field = field_info_field('field_last_newsletter');
// unset the allowed values
$default_stats_field['settings']['allowed_values'] = '';
// function name that provides array of values
$default_stats_field['settings']['allowed_values_function'] = 'my_module_field_last_newsletter_allowed_values';
// update value with new value.
field_update_field($default_stats_field);
}
After saving the new settings, you need to implement the callback function for the dynamic allowed values.
/**
* Allowed values callback for field_last_newsletter.
*/
function my_module_field_last_newsletter_allowed_values() {
// ...
$campaigns = $client->get_campaigns();
$tab_campaign = array(""=>"Dernière newsletter");
foreach ($campaigns->response as $camp){
$tab_campaign[$camp->CampaignID] = $camp->Name;
}
return $tab_campaign;
}

The issue here is that you are defining a field in code and since this was not created through the UI a database table to store it's value was not created. I would suggest you create this field through the UI (/admin/structure/types/manage/xxxxx/fields) and in your hook_form_alter you just change the #options array to populate it. This way a database table for your data will be created and Drupal will handle saving the data, populating the saved value, etc...

Related

TYPO3 ConnectionPool find a file after the uid of the file reference and update data

The concept is that, after a successfull save of my object, it should update a text in the database (With a Hook). Lets call the field 'succText'. The table i would like to access is the sys_file but i only get the sys_file_reference id when i save the object. So i thought i could use the ConnectionPool to select the sys_file row of this file reference and then insert the data on the field 'succText'.
I tried this:
public function processDatamap_preProcessFieldArray(array &$fieldArray, $table, $id, \TYPO3\CMS\Core\DataHandling\DataHandler &$pObj) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
$findItemsId = $queryBuilder
->select('*')
->from('sys_file_reference')
->join(
'sys_file_reference',
'sys_file',
'reference',
$queryBuilder->expr()->eq('reference.uid', $queryBuilder->quoteIdentifier('uid_local'))
)
->where(
$queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter($fieldArray['downloads'], \PDO::PARAM_INT))
)
->execute();
}
But this give me back the sys_file_reference id and not the id and the field values of the sys_file table.
As for the update, i havent tried it yet, cause i haven't figured out yet, how to get the row that needs to be updated. I gues with a subquery after the row is found, i don't really know.
The processDatamap_preProcessFieldArray is going to be renamed to post. I only have it this way in order to get the results on the backend.
Thanks in advance,
You might want to make use of the FileRepository class here.
$fileRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\FileRepository::class);
$fileObjects = $fileRepository->findByRelation('tablename', 'fieldname', $uid);
Where $uid is the ID of the record that the files are connected to via file reference.
You will get back an array of file objects to deal with.
I resolved my problem by removing the first code and adding a filerepository instance.
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$fileObjects = $fileRepository->findByRelation('targetTable', 'targetField', $uid);
VERY IMPORTANT!
If you are creating a new element then TYPO3 assigns a temp UID variable with a name that looks like this NEW45643476. In order to get the $uid from the processDatamap_afterDatabaseOperations you need to add this code before you get the instance of the fileRepository.
if (GeneralUtility::isFirstPartOfStr($uid, 'NEW')) {
$uid = $pObj->substNEWwithIDs[$uid];
}
Now as far as the text concerns, i extracted from a pdf. First i had to get the basename of the file in order to find its storage location and its name. Since i have only one file i don't really need a foreach loop and i can use the [0] as well. So the code looked like this:
$fileID = $fileObjects[0]->getOriginalFile()->getProperties()['uid'];
$fullPath[] = [PathUtility::basename($fileObjects[0]->getOriginalFile()->getStorage()->getConfiguration()['basePath']), PathUtility::basename($fileObjects[0]->getOriginalFile()->getIdentifier())];
This, gives me back an array looking like this:
array(1 item)
0 => array(2 items)
0 => 'fileadmin' (9 chars)
1 => 'MyPdf.pdf' (9 chars)
Now i need to save the text from every page in a variable. So the code looks like this:
$getPdfText = '';
foreach ($fullPath as $file) {
$parser = new Parser();
$pdf = $parser->parseFile(PATH_site . $file[0] . '/' . $file[1]);
$pages = $pdf->getPages();
foreach ($pages as $page) {
$getPdfText .= $page->getText();
}
}
Now that i have my text i want to add it on the database so i will be able to use it on my search action. I now use the connection pool to get the file from the sys_file.
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file');
$queryBuilder
->update('sys_file')
->where(
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($fileID))
)
->set('pdf_text', $getPdfText)
->execute();
Now everytime i choose a PDF from my extension, i save its text on the database.
EXTRA CONTENT
If you want to include the PDFParser as well and you are on composer mode, then add this on your composer.json:
"smalot/pdfparser" : "*"
and on the autoload:
"Smalot\\PdfParser\\" : "Packages/smalot/pdfparser/src/"
Then under: yourExtension/Classes/Hooks/DataHandler.php add the namespace:
use Smalot\PdfParser\Parser;
Now you are able to use the getPages() and getText() functions.
The Documentation
If i missed something let me know and i will add it.

SugarCRM 6.5 CE: how to remove button in detailview according to a condition

I'm trying to remove buttons in detail view of a Lead if it is alredy converted.
I saw a similar question and it use javascript to hide buttons. I'm trying to obtain same result via php.
This is my view.detail.php in custom\modules\Leads\views\ folder
class LeadsViewDetail extends ViewDetail {
function __construct(){
parent::__construct();
}
function preDisplay() {
parent::preDisplay();
if($this->bean->converted==1) {
echo "hide";
foreach ($this->dv->defs['templateMeta']['form']['buttons'] as $key => $value) {
unset($this->dv->defs['templateMeta']['form']['buttons'][$key]);
}
} else {
echo "show";
}
}
}
Using this code, after a Quick Repair & Rebuilt, I see "hide" or "show" correctly according to the Lead status but buttons are not updated correctly.
If I open a converted Lead after QR&R, I will never see the buttons.
If I open a unconverted Lead after QR&R, I will see the buttons all times.
I'm stuck with this situation. Can anyone explain me where is the problem? How I can solve it?
Every help is very appreciated.
You can probably handle this without extending the ViewDetail by using Smarty logic ("customCode") in custom/modules/Leads/metadata/detailviewdefs.php. It looks like the Convert button is already only rendered when the user has Edit privileges, so it's not a big deal to add one more condition to it...
$viewdefs['Leads']['DetailView']['templateMeta']['form]['buttons'][] = array('customCode' => '
{if $bean->aclAccess("edit") && $bean->converted}
<input title="{$MOD.LBL_CONVERTLEAD_TITLE}"
accessKey="{$MOD.LBL_CONVERTLEAD_BUTTON_KEY}"
type="button"
class="button"
name="convert"
value="{$MOD.LBL_CONVERTLEAD}"
onClick="document.location=\'index.php?module=Leads&action=ConvertLead&record={$fields.id.value}\'" />
{/if}');
Alternatively, if you do have several conditions and they'd get too messy or difficult for Smarty logic to be reasonable, we can combine a small amount of Smarty Logic with the extended ViewDetail.
This except of custom/modules/Leads/metadata/detailviewdefs.php is actually the out-of-the-box file from SugarCRM CE 6.5.24, where it looks like they've actually tried to make this customization easier by supplying a Smarty var $DISABLE_CONVERT_ACTION. For reference, it simply needs the global config variable disable_convert_lead to be set and enabled, but I suspect that this was a relatively new feature not included in earlier versions. Still, it's a good example of using the View to set a simple Smarty variable that we can pivot on:
<?php
$viewdefs['Leads']['DetailView'] = array (
'templateMeta' => array (
'form' => array (
'buttons' => array (
'EDIT',
'DUPLICATE',
'DELETE',
array (
'customCode' => '{if $bean->aclAccess("edit") && !$DISABLE_CONVERT_ACTION}<input title="{$MOD.LBL_CONVERTLEAD_TITLE}" accessKey="{$MOD.LBL_CONVERTLEAD_BUTTON_KEY}" type="button" class="button" onClick="document.location=\'index.php?module=Leads&action=ConvertLead&record={$fields.id.value}\'" name="convert" value="{$MOD.LBL_CONVERTLEAD}">{/if}',
//Bug#51778: The custom code will be replaced with sugar_html. customCode will be deplicated.
'sugar_html' => array(
'type' => 'button',
'value' => '{$MOD.LBL_CONVERTLEAD}',
'htmlOptions' => array(
'title' => '{$MOD.LBL_CONVERTLEAD_TITLE}',
'accessKey' => '{$MOD.LBL_CONVERTLEAD_BUTTON_KEY}',
'class' => 'button',
'onClick' => 'document.location=\'index.php?module=Leads&action=ConvertLead&record={$fields.id.value}\'',
'name' => 'convert',
'id' => 'convert_lead_button',
),
'template' => '{if $bean->aclAccess("edit") && !$DISABLE_CONVERT_ACTION}[CONTENT]{/if}',
),
),
We can combine this $DISABLE_CONVERT_ACTION reference with a custom/modules/Leads/views/view.detail.php like the following to set it based on whatever condition we want:
<?php
require_once('modules/Leads/views/view.detail.php');
class CustomLeadsViewDetail extends LeadsViewDetail {
/*
* while we might normally like to call parent::display() in this method to
* best emulate what the parnts will do, we instead here copy-and-paste the
* parent methods' content because LeadsViewDetail::display() will set the
* DISABLE_CONVERT_ACTION Smarty var differently than we want.
*/
public function display(){
global $sugar_config;
// Example One: Disable Conversion when status is Converted
$disableConvert = ($this->bean->status == 'Converted');
// Example Two: Disable Conversion when there is at lead one related Call
// where the status is Held
$disableConvert = FALSE;
$this->bean->load_relationships('calls');
foreach($this->bean->calls->getBeans() as $call){
if($call->status == 'Held'){
$disableConvert = TRUE;
break; // exit foreach()
}
}
// Example Three: Disable Conversion if the User is in a specific Role, e.g.
// Interns who are great for data entry in Leads but shouldn't be making
// actual sales
global $current_user;
$disableConvert = $current_user->check_role_membership('No Lead Conversions');
// In any of the above examples, once we have $disableConvert set up
// as we want, let the Smarty template know.
$this->ss->assign("DISABLE_CONVERT_ACTION", $disableConvert);
// copied from ViewDetail::display();
if(empty($this->bean->id)) {
sugar_die($GLOBALS['app_strings']['ERROR_NO_RECORD']);
}
$this->dv->process();
echo $this->dv->display();
}
}

cakephp form validation for counting multiple textareas as a group

Is it possible to validate a group of form textareas at once? I would like to check that at least 5 out of 15 text areas are notEmpty. Any suggestions on a method for doing this?
If you're going to down vote, explain why.
I've read http://book.cakephp.org/view/150/Custom-Validation-Rules#Adding-your-own-Validation-Methods-152 but it isn't clear to me how I would group multiple field items together and only check for a minimum of 5 notEmpty cases.
Edit: I'm using version 2.3.7
I don't really have any code to show because I'm just trying to do a data validation on a form with many textareas. My form isn't working right now due to other issues. If this was the only problem I could post all the code, but right now it would just confuse matters. I'm looking for a descriptive answer of how to validate a group of fields together.
Attach the validation rule to one textarea
You can do this by attaching the validation rule to any one of the text areas e.g.
class Foo extends AppModel {
public $validate = array(
'textarea_1' => array(
'atLeast5' => array(
'rule' => array('validate5Textareas'),
'message' => 'Please put text in at least 5 of the little boxes'
)
)
);
public function validate5Textareas() {
$filledTextAreas = 0;
// adapt this to match the names/logic of the real form
for ($i = 1; $i <= 15; $i++) {
if (!empty($this->data[$this->alias]['textarea_' . $i])) {
$filledTextAreas++;
}
}
return $filledTextAreas >= 5;
}
}
The $validate array defines a rule such that validate5Textareas is called if textarea_1 is in the data passed to save.
The function validate5Textareas will return true if 5 or more have text in them and false otherwise.

Customizing layout to sfWidgetFormDoctrineChoice

I am using Symfony 1.4 sfWidgetFormDoctrineChoice
I have added the checkboxes to the form, which pulls the Model data successfully. What I want to do is also include a thumbnail next to the checkbox, along with the title.
$this->setWidget('bulkUploadVideos', new sfWidgetFormDoctrineChoice(array(
'model' => 'MediaAsset',
'query' => Doctrine_Query::create()->select('u.url')->from('MediaAsset u')->orderBy('id DESC'),
'add_empty' => false,
'multiple' => true,
'expanded' => true
)
));
This does a fantastic job of pulling the query into a list of checkboxes arranged like so:
⧠ Greenjeans
⧠ Mr Magoo
⧠ Droopy
In the Media Assets table, I also have an image url that I want to include in the layout. SO it would look like this:
|-img thumbnial- | ⧠ Greenjeans
|-img thumbnail- | ⧠ Mr. Magoo
|-img thumbnial- | ⧠ Droopy
I thought maybe using a formatter class, but I do not see any change in the form.
lib/form/formatters/sfWidgetFormSchemaFormatterAllVideos.class.php
<?php
class sfWidgetFormSchemaFormatterAllVideos extends sfWidgetFormSchemaFormatter {
protected
$rowFormat = "<span class=\"my-label-class\">%label%</span>\n <span>%error%%field%%help%%hidden_fields%</span>`n",
$errorRowFormat = "<span class=\"my-error-class\" colspan=\"2\">\n%errors%</span>\n",
$helpFormat = '<br />%help%',
$decoratorFormat = "<div class='custom-video-layout'>\n %content%</div>";
}
and then i put this at the bottom of my MediaAssetsForm.class.php
public function configure() {
parent::configure();
...
..
...
$this->getWidgetSchema()->setFormFormatterName('AllVideos');
Alas, the page layout looks exactly the same. Am I incorrectly calling the Formatter, or is there a much easier way of doing this?
Which btw, still does not answer the question of how I query the image url from the table into the output for each checkbox. That's the main problem I would like to solve. Thumbnails of each record in the form.
The formatter is used to render the whole form, what you need is to change the rendering of one of the widgets.
The sfwidgetFormDoctrineChoice has an option renderer which takes a formatter as an argument. The one that you need is the sfWidgetFormSelectCheckbox. What I would do is:
create your own class which will extend the sfWidgetFormSelectCheckbox class. E.g.
class sfWidgetFormMySelectWithThumbs extends sfWidgetFormSelectCheckbox {
}
Extend the configure function so it takes another option which will hold an array of your thumbnails.
public function configure($options = array(), $arguments = array()) {
parent::configure($options, $arguments);
$this->addOption('thumbnails', array());
}
Change the formatChoices function so it adds the image in front of the checkbox (you can copy and modify the original formatChoices function).
...
$sources = $this->getOption('thumbnails');
...
$inputs[$id] = array(
'input' => sprintf('| %s | %s',
$this->renderTag('img', array('src' => $sources[$key])),
$this->renderTag('input', array_merge($baseAttributes, $attributes))
),
'label' => $this->renderContentTag('label', self::escapeOnce($option), array('for' => $id)),
);
Use the formatter class in your widget:
$this->setWidget('bulkUploadVideos', new sfWidgetFormDoctrineChoice(array(
...
'renderer' => new sfWidgetFormMySelectWithThumbs(array('thumbnails' => $thumbanils))
)
));
Of course you need to retrieve the list of thumbnails as an array where the array keys are the same as the id's used for values for the checkboxes, but that shouldn't be an issue.

SugarCRM Adding Additional Details Info icon in Detail View

in SugarCRM some modules like "Calls" has an "i" (Additional Details) icon in List view which shows some additional details about that record.
I want to display same kind for other modules like customer visits with some custom details of the records.
Any hints or guidance will be helpful.
1) Create a file in your metadata folder {MODULENAME}/metadata/additionalDetails.php. You have to find correct place of your module.
custom/modules/MODULENAME/metadata/
custom/modulebuilder/packages/PACKAGENAME/modules/MODULENAME/metadata/
etc...
2) and create a function something like this. replace {MODULENAME} and {MODULE_BEAN_NAME} with actual module name in all places.
function additionalDetails{MODULE_BEAN_NAME}($fields) {
static $mod_strings;
if(empty($mod_strings)) {
global $current_language;
$mod_strings = return_module_language($current_language, '{MODULENAME}');
}
$overlib_string = '';
if(!empty($fields['NAME']))
$overlib_string .= '<b>'. $mod_strings['LBL_NAME'] . '</b> ' . $fields['NAME'] . ' <br>';
//Add whatever info you want to show up to $overlib_string
$editLink = "index.php?action=EditView&module={MODULENAME}&record={$fields['ID']}";
$viewLink = "index.php?action=DetailView&module={MODULENAME}&record={$fields['ID']}";
return array(
'fieldToAddTo' => 'NAME',
'string' => $overlib_string,
'editLink' => $editLink,
'viewLink' => $viewLink
);
}
you have to create $overlib_string with your data (in html). If you need edit and view links on your modal box you have to return them as well. $fields is an associative array that contains db record.
3) The i icon should appear on the module list view.