implode Warning: array to string conversion in PHP-Nuke Titanium function deepPurifier - warnings

This works without any warnings in every version of PHP except 8
I think they have changed something with implode and I have tried all the examples to no avail.
Perhaps I could get this done some other way. I need some PHP 8 eyes as I'm very new to PHP 8 and up.
The warning in my function is on the following line:
Warning: array to string conversion
$test = implode(', ', $data);
It is very hard to make sense of certain things when you use so many different languages with similar syntax. I fear that this is just a minor brain fart on my part.
This code appears to be working all though I have the warning, I am wondering if this is just a bug in PHP 8 and 8.1 etc
function deepPurifier($data)
{
global $html_auth, $admin;
static $config, $purifier;
# Error check
if(empty($data) || !isset($data))
return $data;
if(!is_array($data))
return stripslashes((string) $data);
// THIS IS WHERE MY WARNING IS
// warning: array to string conversion
$test = implode(', ', $data);
if(!preg_match('[<|>]', $test))
{
return $data;
}
if(!isset($config) || empty($config))
{
set_include_path(NUKE_BASE_DIR. get_include_path() );
require_once(NUKE_VENDOR_DIR.'ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php');
require_once(NUKE_VENDOR_DIR.'ezyang/htmlpurifier/library/HTMLPurifier.autoload.php');
$config = HTMLPurifier_Config::createDefault();
$config->set('Core.Encoding', 'UTF-8');
$config->set('HTML.Doctype', 'HTML 4.01 Transitional');
if(!is_god($admin) || (is_god($admin) && !$html_auth))
{
$config->set('HTML.Trusted', true);
$config->set('HTML.SafeObject', true);
$config->set('HTML.SafeEmbed', true);
$config->set('HTML.AllowedAttributes','img#height,img#width,img#src,iframe#src,iframe#allowfullscreen');
$config->set('HTML.AllowedAttributes', 'src, height, width, alt');
$config->set('HTML.AllowedElements', ['img', 'iframe', 'div', 'script', 'object', 'p', 'span', 'pre', 'b', 'i', 'u', 'strong', 'em', 'sup', 'a', 'img', 'table', 'tr', 'td', 'tbody', 'thead', 'param']);
$config->set('Output.FlashCompat', true);
$config->set('Attr.EnableID', true);
$config->set('Filter.Custom', [new HTMLPurifier_Filter_YouTube()]);
}
$def = $config->getHTMLDefinition(true);
$def->addAttribute('iframe', 'allowfullscreen', 'Bool');
$purifier = new HTMLPurifier($config);
}
# Loop through the data
foreach ($data as $k => $v) {
# If its an array
if (is_array($data[$k])) {
# Go though this function again
$data[$k] = array_map('deepStrip', $data[$k]);
} elseif (is_numeric($v) || empty($v)) {
$data[$k] = $v;
} else {
if (isset($_GET['op']) && $_GET['op'] == 'Configure' && isset($_GET['sub']) && $_GET['sub'] == '11') {
$data[$k] = $v;
continue;
} elseif ($k == 'xsitename' || $k == 'xslogan') {
$data[$k] = $v;
continue;
} elseif (isset($_GET['name'])) {
# If forum post let it pass to the forum html security
if ($_GET['name'] == 'Forums' && (isset($_GET['file']) && ($_GET['file'] == 'posting')) && ($k == 'message' || $k == 'subject')) {
$data[$k] = $v;
continue;
}
# If PM let it pass to the forum html security
if ($_GET['name'] == 'Private_Messages' && ($k == 'message' || $k == 'subject')) {
$data[$k] = $v;
continue;
}
# If SIG let it pass to the forum html security
if ($_GET['name'] == 'Profile' && (isset($_GET['mode']) && ($_GET['mode'] == 'signature')) && $k == 'signature') {
$data[$k] = $v;
continue;
}
}
# If its a strip lets purify it
if (!is_god($admin) || (is_god($admin) && !$html_auth)) {
$data[$k] = $purifier->purify($v);
}
$data[$k] = str_replace('\n', "\n", (string) $data[$k]);
# Get the registered globals also
global ${$k};
if (isset(${$k}) && !empty(${$k})) {
${$k} = $data[$k];
}
}
}
return $data;
}
var_dump($test);
string(20) "Forums,viewtopic,284" string(20) "Forums,viewtopic,284"

The warning appears in PHP 8 when the array being imploded is nested. From the docs, the $array argument should be an array of strings, not an array of arrays.
For example, the following produces no warnings in both PHP 7.4 and PHP 8.1:
$data = ["a", "b"];
print(implode(" ", $data));
Whereas the following gives the warning Array to string conversion (note the arrays within the first array):
$data = ["a", "b" => ["c"], "d" => ["e"]];
print(implode(" ", $data));
You can verify the behaviour with docker and different PHP versions:
docker run --rm php:7.4 -r 'print(implode(" ", ["a", "b" => ["c"], "d" => ["e"]]));'
docker run --rm php:8.1 -r 'print(implode(" ", ["a", "b" => ["c"], "d" => ["e"]]));'
Both produce the output:
a Array Array
But PHP 8 will now raise warnings:
Warning: Array to string conversion in Command line code on line 1

To get rid of the warning you must change the following:
From:
$test = implode(',', $data);
To:
$test = json_encode($data);
Also Change Improper Syntax:
From:
if(!preg_match('[<|>]', $test))
{
return $data;
}
To:
if(!preg_match('/[<|>]/', $test))
{
return $data;
}

Related

convert csv file to json object with Laravel

I am doing a project with Laravel 7. I have to read a csv file and through a controller passing the data in json format to a view.
Unfortunately I don't know how to do that.
These are my controller methods:
public function index($source)
{
$source = strtolower($source);
switch ($source) {
case "csv":
$file_csv = base_path('transactions.csv');
$transactions = $this->csvToJson($file_csv);
dd(gettype($transactions));
return view('transactions', ['source' => $source, 'transactions' => $transactions]);
break;
case "db":
$transactions = Transaction::all();
dd(gettype($transactions));
return view('transactions', ['source' => $source, 'transactions' => $transactions]);
break;
default:
abort(400, 'Bad sintax error.');
}
}
function csvToJson($filename = '', $delimiter = ',')
{
if (!file_exists($filename) || !is_readable($filename)) {
return false;
}
$header = null;
$data = array();
if (($handle = fopen($filename, 'r')) !== false)
{
while (($row = fgetcsv($handle, 1000, $delimiter)) !== false)
{
if (!$header)
$header = $row;
else
$data[] = array_combine($header, $row);
}
fclose($handle);
}
return $data;
}
As you can see, under the two cases I put a dd with a gettype function inside. In the first case I receive uncorrectly the response array, in the second one I receive correctly the response object.
The converted csv file should have this format:
[{"id":1,"code":"T_218_ljydmgebx","amount":"8617.19","user_id":375,"created_at":"2020-01-19T16:08:59.000000Z","updated_at":"2020-01-19T16:08:59.000000Z"},
{"id":2,"code":"T_335_wmhrbjxld","amount":"6502.72","user_id":1847,"created_at":"2020-01-19T16:08:59.000000Z","updated_at":"2020-01-19T16:08:59.000000Z"}]
Do you know how to convert the array transactions into a json object in the first case?
I don't know if there is any build-in solution on Laravel, but it can be done by PHP.
I didn't test my code. So it might not work, or contain some typos, but I'm sure it will give you some directions.
$cols = ["id","code","amount","user_id","created_at","updated_at"];
$csv = file('folder/name.csv');
$output = [];
foreach ($csv as $line_index => $line) {
if ($line_index > 0) { // I assume the the first line contains the column names.
$newLine = [];
$values = explode(',', $line);
foreach ($values as $col_index => $value) {
$newLine[$cols[$col_index]] = $value;
}
$output[] = $newLine;
}
}
$json_output = json_encode($output);
You can just do this :
$csv= file_get_contents($file);
$array = array_map('str_getcsv', explode(PHP_EOL, $csv));
$json json_encode($array);
If you want to return json object
return $json;
If you want to create a .json file
// Pure PHP
$file = fopen('results.json', 'w');
fwrite($file, $json);
fclose($file);
// Laravel using Storage
Storage::disk('local')->put('public/result.json', $json);
I hope this helps somone.
I have an email marketing application where I made bulk importers. So here is their CSV TO JSON code.
function convert_csv_to_json($csv_data){
// CSV to JSON process 1
$context = array(
'http'=>array(
'follow_location' => false,
'max_redirects' => 1000000
)
);
$context = stream_context_create($context);
if (($handle = fopen($csv_data, "r", false, $context)) !== FALSE) {
$csvs = [];
while(! feof($handle)) {
$csvs[] = fgetcsv($handle);
}
$datas = [];
$column_names = [];
foreach ($csvs[0] as $single_csv) {
$column_names[] = $single_csv;
}
foreach ($csvs as $key => $csv) {
if ($key === 0) {
continue;
}
foreach ($column_names as $column_key => $column_name) {
$datas[$key-1][$column_name] = $csv[$column_key];
}
}
return $json = json_encode($datas);
}
// OR
// CSV to JSON process 1
$cols = ['id',
'owner_id',
'name',
'email',
'country_code',
'phone',
'favourites',
'blocked',
'trashed',
'is_subscribed',
'deleted_at',
'created_at',
'updated_at.'];
$csv = file($csv_data);
$output = [];
foreach ($csv as $line_index => $line) {
if ($line_index > 0) { // I assume the the first line contains the column names.
$newLine = [];
$values = explode(',', $line);
foreach ($values as $col_index => $value) {
$newLine[$cols[$col_index]] = $value;
}
$output[] = $newLine;
}
}
return $json_output = json_encode($output);
}

fat models and thin controllers in codeigniter

this is a user.php controller
public function verifyLogin() {
if (isset($_POST["email"])) {
$e = $this->input->post("email");
$p = $this->input->post("pass");
$this->form_validation->set_rules("email", "email", "required|valid_email|xss_clean");
$this->form_validation->set_rules("pass", "password", "required|xss_clean");
if ($this->form_validation->run()) {
$data = array(
'select' => '*',
'table' => 'users',
'where' => "email = '$e' AND activated = '1'"
);
$checklogin = $this->query2->selectData($data);
if ($checklogin === FALSE) {
echo "quering userInfo fails. email is wrong or activation not done";
exit();
} else {
foreach ($checklogin as $row) {
$dbid = $row->id;
$dbusername = $row->username;
$dbpassword = $row->password;
$dbemail = $row->email;
if ($p === $dbpassword) {
$login_data = array(
'name' => $dbusername,
'email' => $dbemail,
'password' => $dbpassword,
'id' => $dbid,
'expire' => '86500',
'secure' => TRUE,
'logged_in' => TRUE
);
$this->input->set_cookie($login_data);
$this->session->set_userdata($login_data);
if ($this->session->userdata("logged_in")) {
$time = time();
$now = unix_to_human($time, TRUE, 'us');
$updateLogin = $this->query1->updateLogin($e, $now);
if ($updateLogin) {
echo "success";
} else {
echo 'update failed';
}
} else {
echo "session failed";
}
}else{
echo 'password incorrect';
}
}
}
} else {
echo "form validation fails";
}
} else {
$this->load->view('header');
$this->load->view('login');
$this->load->view('modal');
$this->load->view('footer');
}
}
this is model.php
public function selectData($data){
if(isset($data['direction'])){
$dir = $data['direction'];
}else{
$dir = "ASC";
}
if(isset($data['offset'])){
$off = $data['offset'];
}else{
$off = '0';
}
if(isset($data['select']) && isset($data['table'])){
$this->db->select($data['select'])->from($data['table']);
}
if(isset($data['where'])){
$this->db->where($data['where']);
}
if(isset($data['order_by_name'])){
$this->db->order_by($data['order_by_name'], $dir);
}
if(isset($data['limit'])){
$this->db->limit($data['limit'], $off);
}
$query = $this->db->get();
if($query){
$d = $query->result();
return $d;
}else{
return FALSE;
}
}
is this a good way of quering database?
i am new to mvc and i am reading everywhere about "fat models and this controllers"
what can be done to make it a good mvc architecture?
its only acceptable to echo out from the controller when you are developing:
if ($checklogin === FALSE) {
echo "quering userInfo fails.
if checking login is false then either show a view or go to a new method like
if ($checklogin === FALSE) {
$this->showLoginFailed($errorMessage) ;
the check login code in the controller is a great example of something that could be refactored to a model. then if you need to check login from another controller its much easier. putting the form validation code in a model would be another choice. often times when you are validating form code you are also inserting/updating to a database table -- so having all those details together in a model can make things easier long term.
"fat model" does not mean one method in a model that does a hundred things. it means the controller says -- did this customer form validate and insert to the database? yes or no? 3 lines of code.
the model has the code that is looking into the "fat" details of the form, validation, database, etc etc. say 50 or more lines compared to the 3 in the controller. but the methods in the model should still be clean: small and specific.

Retrieving array of Controllers/Actions

Is it possible in Yii2 to retrieve an array containing all controllers and actions for the whole application?
I finally ended up with:
protected function actionGetcontrollersandactions()
{
$controllerlist = [];
if ($handle = opendir('../controllers')) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != ".." && substr($file, strrpos($file, '.') - 10) == 'Controller.php') {
$controllerlist[] = $file;
}
}
closedir($handle);
}
asort($controllerlist);
$fulllist = [];
foreach ($controllerlist as $controller):
$handle = fopen('../controllers/' . $controller, "r");
if ($handle) {
while (($line = fgets($handle)) !== false) {
if (preg_match('/public function action(.*?)\(/', $line, $display)):
if (strlen($display[1]) > 2):
$fulllist[substr($controller, 0, -4)][] = strtolower($display[1]);
endif;
endif;
}
}
fclose($handle);
endforeach;
return $fulllist;
}
I started with the answer from Andreas Hinderberger, and just fine tuned it. I ended up with something like this:
It uses FileHelper to get all the files recursively, which is useful if you are extending controllers from base classes. It also formats the controller-id/action-id using Inflector::camel2id so they will match your routes.
public function getAllControllerActions()
{
$controllers = \yii\helpers\FileHelper::findFiles(Yii::getAlias('#app/controllers'), ['recursive' => true]);
$actions = [];
foreach ($controllers as $controller) {
$contents = file_get_contents($controller);
$controllerId = Inflector::camel2id(substr(basename($controller), 0, -14));
preg_match_all('/public function action(\w+?)\(/', $contents, $result);
foreach ($result[1] as $action) {
$actionId = Inflector::camel2id($action);
$route = $controllerId . '/' . $actionId;
$actions[$route] = $route;
}
}
asort($actions);
return $actions;
}
As far as I know Yii 2 doesn't have any built-in methods to achieve this. You can only get the current controller and its action.
What's the purpose of it? If you really need this you can write such functionality by yourself.
To get all controllers you should search for a files ending with Conroller. And they can be located in different places of application. For example in nested folders, modules, nested modules, etc. So there is more than just one place for search.
To get all actions you should search for all methods prefixed with action in each controller.
Also don't forget about attached actions in controller actions() method. In framework they are usually ending with Action, take a look for example at rest actions. But no one forces you to name it like that, so there is possibility that some external actions can just have different naming convention (for example if you working in team and don't follow this convention).
And you probably need to exclude such folders as vendor.
So it's not trivial task, but possible with some inaccuracies. I just don't get it what's the point of that.
Follow example walk trought all modules and collect all modules controller actions (no tested):
<?php
$controllerDirs = [];
$controllerDirs[] = \Yii::getAlias('#app/controllers');
if ($commonControllerDir = \Yii::getAlias('#common/controllers', false)) {
$controllerDirs['common'] = $commonControllerDir;
}
foreach (\Yii::$app->modules as $moduleId => $module) {
/*
* get module base path
*/
if (method_exists($module, 'getBasePath')) {
$basePath = $module->getBasePath();
} else {
$reflector = new \ReflectionClass($module['class']);
$basePath = StringHelper::dirname($reflector->getFileName());
}
$basePath .= '/controllers';
$controllerDirs[$moduleId] = $basePath;
}
$actions = [];
foreach ($controllerDirs as $moduleId => $cDir) {
$actions[$moduleId][$cDir] = actionGetcontrollersandactions($cDir);
}
print_r($actions);
function actionGetcontrollersandactions($controllerDir) {
$controllerlist = [];
if ($handle = opendir($controllerDir)) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != ".." && substr($file, strrpos($file, '.') - 10) == 'Controller.php') {
$controllerlist[] = $file;
}
}
closedir($handle);
}
asort($controllerlist);
$fulllist = [];
foreach ($controllerlist as $controller):
$handle = fopen($controllerDir . '/' . $controller, "r");
if ($handle) {
while (($line = fgets($handle)) !== false) {
if (preg_match('/public function action(.*?)\(/', $line, $display)):
if (strlen($display[1]) > 2):
$fulllist[substr($controller, 0, -4)][] = strtolower($display[1]);
endif;
endif;
}
}
fclose($handle);
endforeach;
return $fulllist;
}

How can I gracefully iterate over a key - value hash generated from JSON containing values set to 'undef' and 'null'?

I am pulling a third party's json response and sometimes the values of the fields are literally 'undef' or 'null'. If I try to do a print of the key and value of each object in this json, whenever there is a undef value it will throw an uninitialized value error.
Is there something I can add to the initial $json->decode to change those null/undefs to something perl can handle? Or maybe even just have it exclude the value pairs that are null/undef from being deposited into $json_text?
my $json_text = $json->decode($content);
foreach my $article(#{$json_text->{data}->{articles}}){
while (my($k, $v) = each ($article)){
print "$k => $v\n";
}
}
$_ // "" will translate undef values to empty string,
my $json_text = $json->decode($content);
foreach my $article (#{$json_text->{data}->{articles}}) {
while (my($k, $v) = map { $_ // "" } each %$article) {
print "$k => $v\n";
}
}
Since you are running a version of Perl that allows each to be applied to a hash reference, you can also use the defined-or operator //.
An expression like a // b evaluates to a if a is defined, otherwise b.
You can use it like this.
my $json_text = $json->decode($content);
for my $article (#{$json_text->{data}{articles}}) {
while (my ($k, $v) = each $article) {
printf "%s => %s\n", $k, $v // 'null';
}
}
Try printf "%s => %s\n", $k || "empty", $v || "empty";
or even
$k ||= "empty";
$v ||= "empty";
print "$k => $v\n";

json formatting string to number

My Json output generates;
[
{
"a1_id":"7847TK10",
"output2":"7847TK10",
"output4":"something",
"output5":"3stars.gif",
"output9": "269000",
...
etc. etc.
The google visualization api asks for a number format for the output9 element e.g.:
"output9": 269000 instead of "output9": "269000". How can I achieve this for this element?
My json.php generates the json output like this:
?>
{
"total": <?php echo $total ?>,
"success": true,
"rows": [
// Iterate over the rows
$nextRow= $result->nextRow();
$r = 1;
$info = array();
while ( $nextRow ) {
$nextColumn = $result->nextColumn();
// Has this column been printed already
if ( $unique )
{
$d = $result->getDataForField($unique);
if ( array_key_exists($d, $already) )
{
$nextRow= $result->nextRow();
continue;
}
$already[$d] = true;
}
echo '{';
// Iterate over the columns in each row
while ( $nextColumn )
{
// Get the variable
$variable = $result->getOutputVariable();
$name = $variable->getName(true);
$data = $result->getDataForField();
if ( !isset($info[$name]) ) {
$info[$name]['translate'] = $variable->shouldTranslate();
$info[$name]['type'] = $variable->getDataType();
$info[$name]['linkable'] = $variable->isLinkable();
}
// Translate the data if requested
if ( $info[$name]['translate'] ) {
$data = LQMTemplate::_($data);
}
$data = $variable->format($data, false);
$type = $info[$name]['type'];
if ( ($type == 'bool') or ($type == 'boolean') )
{
$data = $data ? '1' : '0';
echo "'$name':$data";
} elseif ( $encode ) {
// Can we use json_encode ?
// str_replace because some versions of PHP have a bug that will over escape forward slashes
echo "\"$name\":".str_replace('\\/', '/', json_encode($data));
} else {
$data = LQMUtility::jsonEscape($data, '"');
//echo "'$name':\"$data\"";
echo "\"$name\":\"$data\"";
}
// Conditionally print the next column
$nextColumn = $result->nextColumn();
if ( $nextColumn ) echo ",\n ";
}
// Conditionally print the next column
$nextRow = $result->nextRow();
echo $nextRow ? "},\n" : "}\n";
$r++;
}
unset($result);
echo ']}';
}
}
This depends on how you are generating your JSON.
For example, if you were using a Ruby backend, you could call:
"output9" => output9.to_i
There are various helper methods in different languages (e.g. Java and Javascript have parseInt() functions) to change a string into an integer.
Edit:
If your JSON is being generated by PHP, cast the string to an integer:
$json['output9'] = int($output9_value);
That should get rid of the quotation marks.