i have configuration files for my scripts containing huge data structures, e.g:
foo => 'bar',
one => 'two',
bla => [
'something',
'else',
],
bli => {
here => {
'and' => 'there',
'also' => 'here',
},
},
etc.
i'm loading these configuration files using a simple e.g. :
%hash = do 'config.file';
my problem is : when users modify this configuration file, they can mess it up, forgetting a comma, bracket or anything else, it can have terrible impacts as the variables will appear empty afterwards in the script (i don't want to check for the presence of every variable..)
is there any way to "validate" the syntax of such a file/structure at loading time or right after loading it ? any module doing this ?
i suppose i could add a value at the bottom and check for the presence of this value but it's a bit dirty.
thank you !
Is this supposed to be readable with Perl? (then shouldn't there be a comma after the b1 entry?) Then the way to validate it is pass it to eval and then check the value of $#. The less insecure way is to format your configuration in another popular, readable, writeable, comprehendable data serialization format. The JSON format meets those criteria.
Rx is intended to validate the schema of JSON/YAML documents but because it runs on the in-memory data structure rather than the file it's usable with arbitrary config file formats. (It also means that it can't make certain distinctions like the use of true vs. 1 in YAML/JSON.)
Related
I have the following array of hashes in hiera:
corporate_roles:
- name: 'user.1'
system_administrator: true
global_administrator: false
password: TestPassword1234
- name: 'user.2'
system_administrator: true
global_administrator: true
password: TestPassword1234
I need to extract a list of users with a give role (eg global_administrator) to be assigned later on.
I managed to use the map function to extract the data I need:
$corporate_roles = lookup('corporate_roles')
$global_admins = $corporate_roles.map | $hash | { if ($hash['global']){$hash['name']}}
notify { "global admins are: ${global_admins}":
}
However this results in undef values seemingly making their way into the array for the users that don't match the criteria:
Notice: /Stage[main]/salesraft_test/Notify[global admins are: [, user.2]]/message: defined 'message' as 'global admins are: [, user.2]'
Notice: Applied catalog in 0.04 seconds
I can get around this by using the filter function as such:
$test = $global_admins.filter | $users | {$users =~ NotUndef}
Which results in clean output:
Notice: /Stage[main]/salesraft_test/Notify[global admins are: [user.2]]/message: defined 'message' as 'global admins are: [user.2]'
Notice: Applied catalog in 0.03 seconds
But I suspect there must be a better way of doing this and I am either missing some logic in my map or I am likely using the wrong function altogether for this.
I would like to know if there is a better way to achieve what I am trying to do?
But I suspect there must be a better way of doing this and I am either
missing some logic in my map or I am likely using the wrong function
altogether for this.
map() emits exactly one output item for each input item, so if your objective is to apply a single function to obtain your wanted output from your (lengthier) input, then indeed, map will not achieve that.
I would like to know if there is a better way to achieve what I am trying to do?
Personally, I would do the job by filtering out the hashes you want from your input and then mapping those to the wanted output form (as opposed to mapping and then filtering the result):
$global_admins = $corporate_roles.filter |$hash| {
$hash['global_administrator']
}.map |$hash| { $hash['name'] }
I like that because it's nice and clear, but if you want to do it with one function call instead of two then you're probably looking for reduce:
$global_admins = $corporate_roles.reduce([]) |$admins, $hash| {
$hash['global_admin'] ? {
true => $admins << $hash['name'],
default => $admins
}
}
I am trying to create a table and populate the table with the following fields with the help of database seeder:
option a
option b
option c
option d
correct option
First four fields will be assigned random word, and the last field 'correct option' will contain any one of the first four.
I could not find any solution to do it with Laravel database seeder. Can anyone help?
Something like this?
use faker random element function in your factory or seeder.
$optionA = $faker->word;
$optionB = $faker->word;
$optionC = $faker->word;
$optionD = $faker->word;
return [
'option_a' => $optionA,
'option_b' => $optionB,
'option_c' => $optionC,
'option_d' => $optionD,
'correct_option' => $faker->randomElement([$optionA,$optionB,$optionC,$optionD]),
];
Create a factory and use Faker to generate the random words you're after
This sounds like an ideal use case for JSON columns (both for questions and answers). For instance, you might decide to have multiple valid answers to a single multiple choice question.
In your migration:
// create_questions_table.php
...
$table->json('choices')->default(new Expression('(JSON_ARRAY())'));
$table->json('answer')->default(new Expression('(JSON_ARRAY())'));
From https://laravel.com/docs/7.x/migrations#column-modifiers:
Using an Expression instance will prevent wrapping the value in quotes and allow you to use database specific functions. One situation where this is particularly useful is when you need to assign default values to JSON columns.
Then create a factory:
// QuestionFactory.php
$factory->define(Location::class, function (Faker $faker) {
$choices = $faker->words(4);
$answer = [ $choices[rand(1,4)] ];
return [
'choices' => $choices,
'answer' => $answer,
];
});
Using the Faker library included in Laravel, we can pick 4 words and randomly assign one of them to be the answer.
I have a search engine which calls a Cakephp action and receives which model the engine should search in eg. "Projects". The variable is called $data_type;
Right now I use this to check if the model exists:
// Check if Table really exists
if(!TableRegistry::get($data_type)){
// Send error response to view
$response = [
'success' => false,
'error' => 'Data type does not exist'
];
$this->set('response', $response);
return;
}
I'm not sure I'm doing it the right or the safest way to check if a model exists, because I don't know if the TableRegistry::get() function is vulnerable to SQL injection behind the scenes.
I also found that inputing an empty string to the get() function doesn't need in a false result??? Is there a safe solution I can implement that will solve my problem?
TableRegistry::get() is not safe to use with user input
First things first. It's probably rather complicated to inject dangerous SQL via TableRegistry::get(), but not impossible, as the alias passed in the first argument will be used as the database table name in case an auto/generic-table instance is created. However the schema lookup will most likely fail before anything else, also the name will be subject to inflection, specifically underscore and lowercase inflection, so an injection attempt like
Foo; DELETE * FROM Bar;
would end up as:
foo;d_e_l_e_t_e*f_r_o_m_bar;
This would break things as it's invalid SQL, but it won't cause further harm. The bottom line however is that TableRegistry::get() cannot be regarded as safe to use with user input!
The class of the returned instance indicates a table class' existence
TableRegistry::get() looks up and instantiates possible existing table classes for the given alias, and if that fails, it will create a so called auto/generic-table, which is an instance of \Cake\ORM\Table instead of an instance of a concrete subclass thereof.
So you could check the return value against \Cake\ORM\Table to figure whether you've retrieved an instance of an actual existing table class:
$table = TableRegistry::get($data_type);
if (get_class($table) === \Cake\ORM\Table::class) {
// not an existing table class
// ...
}
Use a whitelist
That being said, unless you're working on some kind of administration tool that explicitly needs to be able to access to all tables, the proper thing do would be to use some sort of whitelisting, as having users arbitrarily look up any tables they want could be a security risk:
$whitelist = [
'Projects',
'...'
];
if (in_array($data_type, $whitelist, true) !== true) {
// not in the whitelist, access prohibited
// ...
}
Ideally you'd go even further and apply similar restrictions to the columns that can be looked up.
You may want to checkout https://github.com/FriendsOfCake/awesome-cakephp#search for some ready made search plugins.
From my module, I'm looking for a way to change text-fields value during rendering process, but WITHOUT creating a new formatter, and BEFORE the currently affected formatter works.
In other words I want my changes always made on any text-field, as a generic preparatory step, regardless of which formatter will work afterwards.
For this to work:
I first considered using hook_field_formatter_prepare_view().
To get it invoked, I wanted to use hook_field_formatter_info_alter()
to add my module name to each involved formatter found here. But it
appears that the "module" index only accepts a unique module-name,
not an array.
BTW I'm quite surprised by this lack: I seem it should make sense to allow a sequence of formatters, like are allowed a sequence of
filters!
Then I considered using hook_field_prepare_view(), which seemed to
be the best candidate since the doc sayd it runs before the
formatters own hook_field_formatter_prepare_view(). But that
doesn't work either: this hook is invoked only for a field created by
the involved module (this issue had been discussed here).
Any idea? Thanks in advance.
I actually found a pretty way to do what I looked for.
The method is quite invasive but works fine and may be re-used for different cases.
1. To be as clear as possible, first I rephrase my question in terms of a
general use case:
In the rendering process, how to permit a module to change value of one or more
fields (given field-id, given field-type...) before the formatter (if any) do its own job?
2. The problem to accomplish this:
We can't make the module define a new formatter, because only one may
be defined at the same time for the same field
3. The strategy which led me to the desired result:
use hook_field_formatter_info_alter() to run through existing formatters and "graft" my module inside of those where I wish to intervene
(see detail under 4 below)
use hook_field_formatter_prepare_view() to:
(a) execute the required changes in field values
(the job my module is intended to: here it may be done or not, upon all fields of a given type or precisely identified fiels and so on, depending on any detailed needs)
(b) again run through formatters list and, when involved, fire their own hook_field_formatter_prepare_view() if it exists
(see detail under 5 below)
do the same job as in (b) above, successively for each of the other possibly involved hooks of any formatter:
hook_field_formatter_view()
hook_field_formatter_setting_form()
hook_field_formatter_setting_summary()
4. Detail about how to graft my module in the process:
Whith hook_field_formatter_info_alter(&$info) we face the following $info structure:
$info = array(
['formatter machine name'] = array(
['label'] => 'Human readable formatter description',
['field types'] => array(
[0] => 'a_field_type,
[1] => 'another_field_type',
# ...
),
['settings'] => array(
['option A'] => 'option A value',
['option B'] => 'option B value',
# ...
),
['module'] => 'formatter_module_name',
),
['formatter machine name'] = array(
# ...
),
# ...
);
We can easily run through the formatters list and look at "field types" index to select which ones are concerned by our needs.
Then for each involved one, we can:
substitute our own module name to formatter module name in "module" index
add a new sub-index in "settings" index (say "our module graft") to register the original formatter module name
So our hook_field_formatter_info_alter() will be something like:
function mymodule_field_formatter_info_alter(&$info) {
if($info) {
foreach($info as $name=>$formatter) {
if(
!#$formatter['settings']['mymodule graft'] # or already grafted
and
array_intersect($formatter['field types'],
array('text','text_long','text_with_summary')) # here it is for text fields only
) {
# substitute mymodule to original module:
$info[$name]['settings']['mymodule graft']=$formatter['module'];
$info[$name]['module']='mymodule';
}
}
}
}
Once flushing class registry, now all involved fields have their formatting phase redirected to our own module.
NOTE: installing a new formatter now requires flushing class registry again, in order our module to take it in hand also.
5. Detail about how to make original formatters to work after us:
As stated above, now it is our own module which is notified when a field has to been formatted, rather than the originally affected formatter.
So we must react in our hook_field_formatter_prepare_view(), which should look like:
function mymodule_field_formatter_prepare_view(
$entity_type,$entities,$field,$instances,$langcode,&$items,$displays
) {
# here we do our own job with field values:
if($items) {
foreach($items as $nid=>$node_data) {
# ...
}
}
# then we give original formatter a chance to execute its own hook:
foreach($displays as $display) {
$hook=
$display['settings']['mymodule graft'].'_field_formatter_prepare_view';
if(function_exists($hook)) {
$hook(
$entity_type,$entities,$field,$instances,$langcode,$items,$displays
);
}
}
}
Finally we also must give a chance to other formatters hooks to execute.
For each one, it should look like (replace HOOK and ARGS by the right data for each hook):
function mymodule_field_formatter_HOOK(ARGS) {
$hook=$display['settings']['mymodule graft'].'_field_formatter_HOOK';
if(function_exists($hook)) {
return $hook(ARGS);
}
}
Hope this helps...
I'm having a hard time outputting some array data to an XML file. Here's the workflow:
Get all relevant data (in this case, a collection of videos and the necessary taxonomy) from the DB.
Loop each returned object, cleaning it up a bit (field combinations, etc).
Loop each returned object, returning an XML node by use of a template file (templates/module_name_xml_entity.tpl.php).
Put all the XML nodes into a wrapper XML template (templates/module_name_xml_wrapper.tpl.php).
Save the wrapper (now including the repeated nodes) into a file on the filesystem.
I have been able to complete this workflow if I manually write XML inline (eg: $xml .= ' ' . $data['field'] . '';
That's not optimal however, and I've been asked to use render arrays instead (and to keep my template files within the module).
So, #'s 1, 2, 5 I can figure out (since saving a file is the same). It's #3 that is the real bugger.
My code:
The dump from the db query results in an array of video objects (title, thumbnail, tags, etc). I convert that to the following:
Array (
[#template] => module_name_xml_entry,
[#video] => stdClass Object (
[title], [thumbnail]....
),
[#theme] => module_name_xml_entry,
)...
Now here's something interesting: if I dd() the array (there's 990 of them), I see that "#children" and "#printed" has been added automagically, therefore I assume I'm working with a real render array.
I then try every darn way I can think of to convert this array into XML. I've tried $xmlOut .= render($theStuffAbove), drupal_render($youguessedit), please_lord_make_it_go($facepalm)... no avail.
What I get out is either blank (nothing is in $xmlOut) or the array itself.
Again, I can loop my DB results, convert the result into XML manually (string building mess) and save all that out just fine. It's the using of render arrays that baffles me. Reading "TDGD7" hasn't helped (there's only a few short pages out of 1047 on render arrays), and I'm just not understanding how render arrays can be "rendered."
Update:
I forgot to mention I do have a module_name_theme(...) function setup:
$items = array();
$items['module_name_admin_settings_form'] = array(...);
$items['module_name_xml_wrapper'] = array(
'variables' => array('videos' => NULL)),
'template' => 'templates/module_name_xml_wrapper',
);
$items['module_name_xml_entity'] = array(
'variables' => array('video' => array()),
'template' => 'templates/module_name_xml_entity',
);
return $items;
Fixed
Ok, so this is odd (swear I'd done this before).
Changed template filenames to use dashes instead of underscores (eg: 'templates/module_name_xml_entry.tpl.php -> module-name-xml-entry.tpl.php)
Changed reference in the hook_theme() to use the dashed names instead
Inside my functions, I used a $variables = array('video' => $video);
I called $output[] = theme('module_name_xml_entry', $variables);
I used $output in my wrapper (module_name_xml_wrapper) to save to the filesystem.
So the long and short: it looks like cleaning up the theme() function and my hook_theme() made the output work finally.