Find YAML by key and change the value - symfony

I want to parse the contents of any .yml file and modify any key.
Let's take the security.yml file, get its contents and use Yaml::parse, which will return an array of arrays with its structure.
$yml='/config/security.yml';
$path = $this->get('kernel')->getRootDir().$yml;
$contents= Yaml::parse(file_get_contents($file));
This outputs
array(1) {
["security"]=>
array(5) {
["encoders"]=>
string(8) "Array(1)"
["role_hierarchy"]=>
string(8) "Array(3)"
["providers"]=>
string(8) "Array(2)"
["firewalls"]=>
string(8) "Array(2)"
["access_control"]=>
string(9) "Array(14)"
}
}
So far so good. Now let's say I want to change the value of security.firewalls.main.pattern. I need to change the array value $contents['security']['firewalls']['main']['pattern'] and then file_put_contents the yaml dump.
My question is how to get and set a parameter value, dynamically, by any key and not hard-code it like above. The Yaml parser doesn't have any way to get a value by its key. I need a way to transform security.firewalls.main.pattern into array keys, somehow. I will delete the cache afterwards, obviously.
I made something in the meanwhile:
$array = Yaml::parse(file_get_contents($file));
$keys=array("security","firewalls","main");
$val = $this->parseYmlArray($array, $keys);
public function parseYmlArray($array, $keys)
{
$newArr = $array;
foreach ($keys as $key) {
$newArr = $newArr[$key];
}
return $newArr;
}
This will return whatever node, but it needs an array in the exact order of the keys depth.
I was thinking initially if I could make a ParameterBag just like the one in the Container. The problem is that I can't seem to find where it is created from an associative array in depth to one like key->value(security.firewalls.main.pattern and its value, for instance). If I try to create a ParameterBag out of an array, it doesn't modify that array, the bag contains the exact same array. I'm trying to find where the array is being parsed and transformed into the one in the container.

Actually, I don't see any alternative to update your configuration values directly.
But, you can achieve this by defining parameters.
For example, you can change your security.firewalls.main.pattern like follows:
parameters:
firewall_main_pattern: ^/yourpattern
# ...
firewalls:
# ...
main:
pattern: %firewall_main_pattern%
# ...
Then, update the parameter as needed:
$this->container->setParameter('firewall_main_pattern', '^/yournewpattern');
Hope this solves to your problem.
Update
In the same kind of your parseYmlArray, you can use the following to change a value :
function assignConfigurationValueByPath(&$array, $path, $value) {
$keys = explode('.', $path);
while ($key = array_shift($keys)) {
$arr = &$arr[$key];
}
$arr = $value;
}
$array = Yaml::parse(file_get_contents($file));
assignConfigurationValueByPath($array, 'security.firewalls.main.pattern, 'new_value');
Now $array is changed to:
array(
'security' => array(
// ...
'firewalls' => array(
// ...
'main' => array(
// ...
'pattern' => 'new_value'
),
),
),
):

Related

SilverStripe translate fieldlabels

I simply use _t() to translate CMS Fields in a DataObject: TextField::create('Title', _t('cms.TitleField', 'Title'));. I thought translating $summary_fields was just as simple, but it's not.
Instead of trying to translate Fields and their accompanying summary_fields seperately, I believe I noticed a better way how these fields are translated using the function FieldLabels as used in SiteTree.
Is there way I can translate these both fields in one place (DRY principle) and apply to both easily by calling the var?
Yes I would certainly say the use of FieldLabels is for localisation / translation because of the comment "Localize fields (if possible)" here in the DataObject code...
public function summaryFields() {
$fields = $this->stat('summary_fields');
// if fields were passed in numeric array,
// convert to an associative array
if($fields && array_key_exists(0, $fields)) {
$fields = array_combine(array_values($fields), array_values($fields));
}
if (!$fields) {
$fields = array();
// try to scaffold a couple of usual suspects
if ($this->hasField('Name')) $fields['Name'] = 'Name';
if ($this->hasDatabaseField('Title')) $fields['Title'] = 'Title';
if ($this->hasField('Description')) $fields['Description'] = 'Description';
if ($this->hasField('FirstName')) $fields['FirstName'] = 'First Name';
}
$this->extend("updateSummaryFields", $fields);
// Final fail-over, just list ID field
if(!$fields) $fields['ID'] = 'ID';
// Localize fields (if possible)
foreach($this->fieldLabels(false) as $name => $label) {
// only attempt to localize if the label definition is the same as the field name.
// this will preserve any custom labels set in the summary_fields configuration
if(isset($fields[$name]) && $name === $fields[$name]) {
$fields[$name] = $label;
}
}
return $fields;
}

Filter ModelAdmin by many_many relation

I'm managing the DataObject class 'trainer' with ModelAdmin. A trainer has a many_many relation to my other class 'language'.
On my 'trainer' class I'm manipulating the 'searchableFields' function to display a ListboxField in the filters area.
public function searchableFields() {
$languagesField = ListboxField::create(
'Languages',
'Sprachen',
Language::get()->map()->toArray()
)->setMultiple(true);
return array (
'Languages' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Sprachen',
'field' => $languagesField
)
);
}
That works like expected and shows me the wanted ListboxField. The Problem is, after selecting 1 or 2 or whatever languages and submitting the form, I'm receiving
[Warning] trim() expects parameter 1 to be string, array given
Is it possible here to filter with an many_many relation? And if so, how? Would be great if someone could point me in the right direction.
Update:
Full Error Message: http://www.sspaste.com/paste/show/56589337eea35
Trainer Class: http://www.sspaste.com/paste/show/56589441428d0
You need to define that logic within a $searchable_fields parameter instead of the searchableFields() which actually constructs the searchable fields and logic.
PHP would be likely to throw an error if you go doing fancy form stuff within the array itself, so farm that form field off to a separate method in the same DataObject and simply call upon it.
See my example, I hope it helps.
/* Define this DataObjects searchable Fields */
private static $searchable_fields = array(
'Languages' => array (
'filter' => 'ExactMatchFilter',
'title' => 'Sprachen',
'field' => self::languagesField()
)
);
/* Return the searchable field for Languages */
public function languagesField() {
return ListboxField::create(
'Languages',
'Sprachen',
Language::get()->map()->toArray()
)->setMultiple(true);
}
Yes, it's possible. You just need to override two methods - one in Trainer data object and one in TrainerModelAdmin. First one will make a field, second one will do filtering.
Trainer Data Object:
public function scaffoldSearchFields($_params = null)
{
$fields = parent::scaffoldSearchFields($_params);
// get values from query, if set
$query = Controller::curr()->request->getVar('q');
$value = !empty($query['Languages']) && !empty($query['Languages']) ? $query['Languages'] : array();
// create a field with options and values
$lang = ListboxField::create("Languages", "Sprachen", Language::get()->map()->toArray(), $value, null, true);
// push it to field list
$fields->push($lang);
return $fields;
}
Trainer Model Admin
public function getList()
{
$list = parent::getList();
// check if managed model is right and is query set
$query = $this->request->getVar('q');
if ($this->modelClass === "Trainer" && !empty($query['Languages']) && !empty($query['Languages']))
{
// cast all values to integer, just to be sure
$ids = array();
foreach ($query['Languages'] as $lang)
{
$ids[] = (int)$lang;
}
// make a condition for query
$langs = join(",", $ids);
// run the query and take only trainer IDs
$trainers = DB::query("SELECT * FROM Trainer_Languages WHERE LanguageID IN ({$langs})")->column("TrainerID");
// filter query on those IDs and return it
return $list->filter("ID", $trainers);
}
return $list;
}

Generate url aliasing based on taxonomy term

I have a vocab category and four terms within it. what i want to do is if content is tagged with a termin in particular say "term1" to have the url generated as word1/[node:title] and for all the other tags just the standard url formatting.
If i wanted the term in the url obviously id use pattern replacement but i want another word to be used if a particular tag is used
I can't think of an easy plug-and-play way of achieving this. You may have to create your own token for the "Default path pattern" in Pathauto's URL alias settings:
/**
* Implementation of hook_token_info().
*/
function MODULE_token_info() {
$info['tokens']['node']['node-term-path'] = array(
'name' => t('Node path by term'),
'description' => t('The path to a node based on its taxonomy terms.'),
);
return $info;
}
/**
* Implementation of hook_tokens().
*/
function MODULE_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
if ($type == 'node' && !empty($data['node'])) {
$node = $data['node'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'node-term-path':
$items = field_get_items('node', $node, 'TAXONOMY_FIELD_NAME');
foreach ($items as $item) {
$tids[] = $item['tid'];
}
if (in_array(TID_OF_TERM1, $tids)) {
// Path for nodes with term1
$replacements[$original] = 'word1/'. pathauto_cleanstring($node->title);
}
else {
// Path for other nodes
$replacements[$original] = 'content/'. pathauto_cleanstring($node->title);
}
break;
}
}
}
return $replacements;
}
Found a simple way actually to anyone who need a similar solution use the module Entity Reference.
http://drupal.org/project/entityreference
I just created a new field for the user account select entity reference then you can choose any entity within drupal to reference.
(ie so you can select a term/content/anything)

Search hook for filtering results?

I have been going through the docs and source code looking for something without luck.
Is there a Drupal 6 hook that gets called after hook_search(), but before the $results gets handed off to the template system?
I need to do a fairly custom pruning and reordering of results that get returned. I could just reimplement hook_search(), but this seems like overkill.
Thanks.
There isn't; search_view() (which displays the results) calls search_data(), which invokes hook_search() then immediately themes the results. Re-implementing hook_search() is probably the most straightforward route.
With that said, you could instead implement hook_menu_alter() and have the search page call your custom function instead of calling search_view() (and subsequently calling search_data()). Something like:
function test_menu_alter(&$items) {
$items['search']['page callback'] = 'test_search_view';
foreach (module_implements('search') as $name) {
$items['search/' . $name . '/%menu_tail']['page callback'] = 'test_search_view';
}
}
// Note: identical to search_view except for --- CHANGED ---
function test_search_view($type = 'node') {
// Search form submits with POST but redirects to GET. This way we can keep
// the search query URL clean as a whistle:
// search/type/keyword+keyword
if (!isset($_POST['form_id'])) {
if ($type == '') {
// Note: search/node can not be a default tab because it would take on the
// path of its parent (search). It would prevent remembering keywords when
// switching tabs. This is why we drupal_goto to it from the parent instead.
drupal_goto('search/node');
}
$keys = search_get_keys();
// Only perform search if there is non-whitespace search term:
$results = '';
if (trim($keys)) {
// Log the search keys:
watchdog('search', '%keys (#type).', array('%keys' => $keys, '#type' => module_invoke($type, 'search', 'name')), WATCHDOG_NOTICE, l(t('results'), 'search/'. $type .'/'. $keys));
// Collect the search results:
// --- CHANGED ---
// $results = search_data($keys, $type);
// Instead of using search_data, use our own function
$results = test_search_data($keys, $type);
// --- END CHANGED ---
if ($results) {
$results = theme('box', t('Search results'), $results);
}
else {
$results = theme('box', t('Your search yielded no results'), search_help('search#noresults', drupal_help_arg()));
}
}
// Construct the search form.
$output = drupal_get_form('search_form', NULL, $keys, $type);
$output .= $results;
return $output;
}
return drupal_get_form('search_form', NULL, empty($keys) ? '' : $keys, $type);
}
// Note: identical to search_data() except for --- CHANGED ---
function test_search_data($keys = NULL, $type = 'node') {
if (isset($keys)) {
if (module_hook($type, 'search')) {
$results = module_invoke($type, 'search', 'search', $keys);
if (isset($results) && is_array($results) && count($results)) {
// --- CHANGED ---
// This dsm() is called immediately after hook_search() but before
// the results get themed. Put your code here.
dsm($results);
// --- END CHANGED ---
if (module_hook($type, 'search_page')) {
return module_invoke($type, 'search_page', $results);
}
else {
return theme('search_results', $results, $type);
}
}
}
}
}
You can use hook_search_page() to reorder or format the search result.
Hook search_execute allows you to modify the query in the way you needed. You can even fire new queries with custom sql, for example:
function mymodule_search_execute($keys = NULL, $conditions = NULL) {
// Do some query here.
$result = my_fancy_query();
// Results in a Drupal themed way for search.
$results[] = array(
'link' => (string) $result->U,
'title' => $title,
'snippet' => $snippet,
'keys' => check_plain($keys),
'extra' => array($extra),
'date' => NULL,
);

get vocabulary id by name

I can retrieve a vocabulary id directly from DB,
but is there a built in function for this?
for example:
i have a vocabulary called "listing",
i need that built in function takes "listing" as function argument, and return
a vid.
i am using drupal 6
I have a function for this, well almost..
/**
* This function will return a vocabulary object which matches the
* given name. Will return null if no such vocabulary exists.
*
* #param String $vocabulary_name
* This is the name of the section which is required
* #return Object
* This is the vocabulary object with the name
* or null if no such vocabulary exists
*/
function mymodule_get_vocabulary_by_name($vocabulary_name) {
$vocabs = taxonomy_get_vocabularies(NULL);
foreach ($vocabs as $vocab_object) {
if ($vocab_object->name == $vocabulary_name) {
return $vocab_object;
}
}
return NULL;
}
If you want the vid just get the vid property of the returned object and.
$vocab_object = mymodule_get_vocabulary_by_name("listing");
$my_vid = $vocab_object->vid;
Henriks point about storing it in a variable is very valid as the above code you won't want to be running on every request.
Edit
Also worth noting that in Drupal 7 you can use taxonomy_vocabulary_get_names() which makes this a little easier.
For Drupal 7 if you know the vocabulary machine name this is the way:
$vid = taxonomy_vocabulary_machine_name_load('your_vocabulary_name')->vid;
If you know only the Real name of vocabulary, you can use this function:
function _get_vocabulary_by_name($vocabulary_name) {
// Get vocabulary by vocabulary name.
$query = db_select('taxonomy_vocabulary', 'tv');
$query->fields('tv', [
'machine_name',
'vid',
]);
$query->condition('tv.name', $vocabulary_name, '=');
$vocabulary = $query->execute()->fetchObject();
return $vocabulary;
}
There is no built in function for this, afaik. You can roll your own by calling taxonomy_get_vocabularies() and search for your name in the resulting array, but this will do a database request on every call.
If you have a vocabulary that you often use from code, it might be easier/more effective to store the vid in a Drupal variable via variable_set() once and get it back via variable_get() (Many modules that create a vocabulary on install do it this way).
Edit: here is some sample code to do this on module install.
function mymodule_install() {
$ret = array();
$vocabulary = array(
'name' => t('myvocab'),
'multiple' => '1',
'required' => '0',
'hierarchy' => '1',
'relations' => '0',
'module' => 'mymodule',
'nodes' => array('article' => 1),
);
taxonomy_save_vocabulary($vocabulary);
$vid = $vocabulary['vid'];
variable_set('mymodule_myvocab', $vid);
return $ret
}
Should help.
function _my_module_vid($name) {
$names = taxonomy_vocabulary_get_names();
return $names[$name]->vid;
}
You know the node type to which the vocaulbary is associated. So just use taxonomy_get_vocabularies() and pass the node type as argument and you will get the details you want!
In Drupal 7 you could use:
$vocab_object = taxonomy_vocabulary_machine_name_load('vocabulary_name');
$my_vid = $vocab_object->vid;

Resources