function deleteNews($selected) {
$file = file_get_contents(\'news.json\', true);
$data=json_decode($file,true);
unset($file);
foreach($selected as $ind
TL;DR PHP arrays with all numerical keys, starting at zero, sorted and without holes are the only kind that will get rendered into a JSON array [ "square", "brackets" ]
. All other kinds will become JSON dictionaries { "curly": "brackets" }
.
The reason for the behaviour you observe, and the reason why array_values
appears to fix the problem (and in this case, actually does), is in the difference between PHP and JSON arrays and dictionaries.
This is a PHP array with contiguous numerical keys:
$a = array( "Apple", "Banana", "Canteloupe" );
This is really
$a = array( 0 => "Apple", 1 => "Banana", 2 => "Canteloupe" );
In JSON this becomes an array:
[ "Apple", "Banana", "Canteloupe" ]
This instead is a PHP array with text keys (hash):
$a = array( "a" => "Apple", "b" => "Banana", "c" => "Canteloupe" );
In JSON this is a dictionary (note the curly braces):
{ "a": "Apple", "b": "Banana", "c": "Canteloupe" }
Some operations change a PHP-array-rendered-as-array (ARA) to a -rendered-as-dictionary (ARD).
And these operations are:
Adding a TEXT key to a NUMERIC array.
Having numeric keys NOT in uninterrupted sorted sequence 0...N.
So these seem to be arrays, but are actually dictionaries:
$a = array ( 2 => "Canteloupe", 1 => "Banana", 0 => "Apple" );
$a = array ( 0 => "Apple", 2 => "Canteloupe" );
$a = array ( 1 => "Banana", 0 => "Apple" );
And this finally is the reason why deleting a key that is not the last one will thrash your array and make it a dictionary:
0 1 2 3 => remove 3 => 0 1 2 => still an array!
0 1 2 3 => remove 2 => 0 1 3 => NOT an array!
0 1 2 3 => remove 2 => 0 1 3 => not an array => remove 3 => 0 1 => again an array!
If you were to subject the array to any renumbering operation, or to array_values
, you would get a purely numerical array again:
/**
* delete news items given their index.
*
* @param array $selected the list of indexes (e.g. [ 0, 1, 7 ])
* @return nothing
*/
function deleteNews(array $selected = [ ]) {
try {
$news = array_values( // Reorder...
array_diff_key( // ...all keys...
json_decode(file_get_contents('news.json', true), true), // ...in here...
array_flip($selected) // ...that are not in $selected.
)
);
} catch (\Exception $e) {
// TODO handle errors (e.g. file not found and bad JSON)
}
try {
file_put_contents('news.json', json_encode($news));
} catch (\Exception $e) {
// TODO handle errors
}
// $url="./deleteNews.php";
// redirect($url);
}
So if your code had removed the last indexes of your array, things would have appeared to be working. As soon as the unsetting created a hole in the array, or a text key was added...
Same goes for sorting: [ 1 => "B", 0 => "A" ]
is a JSON dictionary. Sort it preserving key associations to [ 0 => "A", 1 => "B" ]
, and it becomes a JSON array.
One thing to note about your code: you read "news.json" with include-path set to true. But then you save it into the current directory. If you have "news.json" anywhere else in your path except in the current directory, you will end up having two news.json files; which of the two is then used depends on the include path itself.
Saving local files, as it happens here with news.json
, is okay, but some precautions are often in order.
In general it's best to place such "variable" files in a directory of their own (e.g. "./cache" or "./temp") with a suitable .htaccess
to prevent direct reading/execution unless it's needed, and so that permissions may be enforced more clearly.
For example one could use a "./data" directory, and have the PHP files everywhere else set to read-only for the web server; and finally instruct, say, the Apache Web server so that this one directory, while writeable by the web server, could not be easily used for exploiting the system:
<directory "/var/www/mysite/htdocs/data">
# You CANNOT ask for /data and have a directory listing. Just in case.
options -Indexes
# You CANNOT save "news.php" and have it executed :-)
php_flag engine off
</directory>
This way, even if someone succeeeded in uploading a file on your system and that file contained executable, malicious PHP code, that code would not be allowed to execute (not directly, at least).
Allowing direct access to files under the control of a third party, even indirectly, is always potentially dangerous - to others if not to you. For example, storing a JSON object with information gathered on a third site. I've just completed a successful attack test on a client's website. Mutatis mutandis,
This exploited a total of five security flaws (for example 4: the authentication token was not linked to the owner's IP address, or was not regenerated at each transaction, and 5: changes to invoicing or delivery did not trigger a request to re-enter the credit card information). While you might think that such security flaws were, well, security flaws, and the client was unaware of them, they actually were known features, there by design, in the name of "usability" and "user convenience". Flaw 2 stems from an evil-counseled use of eval()
in an ancillary library which isn't like to change anytime soon. In the end, changing local data storage capability (flaw 1) is likely to be the only intervention I'll be able to realistically sell to this particular customer.
function deleteNews($selected) { $file = file_get_contents('news.json', true);
$data=json_decode($file,true);
unset($file);
foreach($selected as $index){
unset($data[$index]);
}
foreach($data as $value){
$new_data[] = $value;
}
$result=json_encode($new_data);
file_put_contents('news.json', $result);
echo $result;
unset($result);
$url="./deleteNews.php";
//redirect($url); }
Try it with array_values:
$result = json_encode(array_values($data));