Skip to content
Snippets Groups Projects
Commit 51d83b85 authored by Andreas Gohr's avatar Andreas Gohr
Browse files

Merge pull request #1176 from splitbrain/ioreplaceinfile

Add IO function to replace lines in a file (new)
parents b86bc28a d93ba631
No related branches found
No related tags found
No related merge requests found
<?php
class io_deletefromfile_test extends DokuWikiTest {
function test_delete(){
$file = TMP_DIR.'/test.txt';
$contents = "The\012Delete\012Delete01\012Delete02\012Delete\012DeleteX\012Test\012";
io_saveFile($file, $contents);
$this->assertTrue(io_deleteFromFile($file, "Delete\012"));
$this->assertEquals("The\012Delete01\012Delete02\012DeleteX\012Test\012", io_readFile($file));
$this->assertTrue(io_deleteFromFile($file, "#Delete\\d+\012#", true));
$this->assertEquals("The\012DeleteX\012Test\012", io_readFile($file));
}
}
......@@ -48,6 +48,11 @@ class io_readfile_test extends DokuWikiTest {
$this->assertEquals("The\015\012Test\015\012", io_readFile(__DIR__.'/io_readfile/test.txt.bz2', false));
$this->assertEquals(false, io_readFile(__DIR__.'/io_readfile/nope.txt.bz2'));
$this->assertEquals(false, io_readFile(__DIR__.'/io_readfile/corrupt.txt.bz2'));
// internal bzfile function
$this->assertEquals(array("The\015\012","Test\015\012"), bzfile(__DIR__.'/io_readfile/test.txt.bz2', true));
$this->assertEquals(array_fill(0, 120, str_repeat('a', 80)."\012"), bzfile(__DIR__.'/io_readfile/large.txt.bz2', true));
$line = str_repeat('a', 8888)."\012";
$this->assertEquals(array($line,"\012",$line,"!"), bzfile(__DIR__.'/io_readfile/long.txt.bz2', true));
}
}
\ No newline at end of file
}
File added
File added
<?php
class io_replaceinfile_test extends DokuWikiTest {
protected $contents = "The\012Delete\012Delete\012Delete01\012Delete02\012Delete\012DeleteX\012Test\012";
/*
* dependency for tests needing zlib extension to pass
*/
public function test_ext_zlib() {
if (!extension_loaded('zlib')) {
$this->markTestSkipped('skipping all zlib tests. Need zlib extension');
}
}
/*
* dependency for tests needing zlib extension to pass
*/
public function test_ext_bz2() {
if (!extension_loaded('bz2')) {
$this->markTestSkipped('skipping all bzip2 tests. Need bz2 extension');
}
}
function _write($file){
io_saveFile($file, $this->contents);
// Replace one, no regex
$this->assertTrue(io_replaceInFile($file, "Delete\012", "Delete00\012", false, 1));
$this->assertEquals("The\012Delete00\012Delete\012Delete01\012Delete02\012Delete\012DeleteX\012Test\012", io_readFile($file));
// Replace all, no regex
$this->assertTrue(io_replaceInFile($file, "Delete\012", "DeleteX\012", false, -1));
$this->assertEquals("The\012Delete00\012DeleteX\012Delete01\012Delete02\012DeleteX\012DeleteX\012Test\012", io_readFile($file));
// Replace two, regex and backreference
$this->assertTrue(io_replaceInFile($file, "#Delete(\\d+)\012#", "\\1\012", true, 2));
$this->assertEquals("The\01200\012DeleteX\01201\012Delete02\012DeleteX\012DeleteX\012Test\012", io_readFile($file));
// Delete and insert, no regex
$this->assertTrue(io_replaceInFile($file, "DeleteX\012", "Replace\012", false, 0));
$this->assertEquals("The\01200\01201\012Delete02\012Test\012Replace\012", io_readFile($file));
}
function test_replace(){
$this->_write(TMP_DIR.'/test.txt');
}
/**
* @depends test_ext_zlib
*/
function test_gzwrite(){
$this->_write(TMP_DIR.'/test.txt.gz');
}
/**
* @depends test_ext_bz2
*/
function test_bzwrite(){
$this->_write(TMP_DIR.'/test.txt.bz2');
}
/**
* Test for a non-regex replacement where $newline contains a backreference like construct - it shouldn't affect the replacement
*/
function test_edgecase1()
{
$file = TMP_DIR . '/test.txt';
io_saveFile($file, $this->contents);
$this->assertTrue(io_replaceInFile($file, "Delete\012", "Delete\\00\012", false, -1));
$this->assertEquals("The\012Delete\\00\012Delete\\00\012Delete01\012Delete02\012Delete\\00\012DeleteX\012Test\012", io_readFile($file), "Edge case: backreference like construct in replacement line");
}
/**
* Test with replace all where replacement line == search line - must not timeout
*
* @small
*/
function test_edgecase2() {
$file = TMP_DIR.'/test.txt';
io_saveFile($file, $this->contents);
$this->assertTrue(io_replaceInFile($file, "Delete\012", "Delete\012", false, -1));
$this->assertEquals("The\012Delete\012Delete\012Delete01\012Delete02\012Delete\012DeleteX\012Test\012", io_readFile($file), "Edge case: new line the same as old line");
}
/**
* Test where $oldline exactly matches one line and also matches part of other lines - only the exact match should be replaced
*/
function test_edgecase3()
{
$file = TMP_DIR . '/test.txt';
$contents = "The\012Delete\01201Delete\01202Delete\012Test\012";
io_saveFile($file, $contents);
$this->assertTrue(io_replaceInFile($file, "Delete\012", "Replace\012", false, -1));
$this->assertEquals("The\012Replace\01201Delete\01202Delete\012Test\012", io_readFile($file), "Edge case: old line is a match for parts of other lines");
}
/**
* Test passing an invalid parameter.
*
* @expectedException PHPUnit_Framework_Error_Warning
*/
function test_badparam()
{
/* The empty $oldline parameter should be caught before the file doesn't exist test. */
$this->assertFalse(io_replaceInFile(TMP_DIR.'/not_existing_file.txt', '', '', false, 0));
}
}
<?php
class io_savefile_test extends DokuWikiTest {
/*
* dependency for tests needing zlib extension to pass
*/
public function test_ext_zlib() {
if (!extension_loaded('zlib')) {
$this->markTestSkipped('skipping all zlib tests. Need zlib extension');
}
}
/*
* dependency for tests needing zlib extension to pass
*/
public function test_ext_bz2() {
if (!extension_loaded('bz2')) {
$this->markTestSkipped('skipping all bzip2 tests. Need bz2 extension');
}
}
function _write($file){
$contents = "The\012Write\012Test\012";
$this->assertTrue(io_saveFile($file, $contents));
$this->assertEquals($contents, io_readFile($file));
$this->assertTrue(io_saveFile($file, $contents, true));
$this->assertEquals($contents.$contents, io_readFile($file));
}
function test_write(){
$this->_write(TMP_DIR.'/test.txt');
}
/**
* @depends test_ext_zlib
*/
function test_gzwrite(){
$this->_write(TMP_DIR.'/test.txt.gz');
}
/**
* @depends test_ext_bz2
*/
function test_bzwrite(){
$this->_write(TMP_DIR.'/test.txt.bz2');
}
}
......@@ -127,22 +127,36 @@ function io_readFile($file,$clean=true){
* @author Andreas Gohr <andi@splitbrain.org>
*
* @param string $file filename
* @return string|bool content or false on error
* @param bool $array return array of lines
* @return string|array|bool content or false on error
*/
function bzfile($file){
function bzfile($file, $array=false) {
$bz = bzopen($file,"r");
if($bz === false) return false;
if($array) $lines = array();
$str = '';
while (!feof($bz)){
while (!feof($bz)) {
//8192 seems to be the maximum buffersize?
$buffer = bzread($bz,8192);
if(($buffer === false) || (bzerrno($bz) !== 0)) {
return false;
}
$str = $str . $buffer;
if($array) {
$pos = strpos($str, "\n");
while($pos !== false) {
$lines[] = substr($str, 0, $pos+1);
$str = substr($str, $pos+1);
$pos = strpos($str, "\n");
}
}
}
bzclose($bz);
if($array) {
if($str !== '') $lines[] = $str;
return $lines;
}
return $str;
}
......@@ -191,13 +205,7 @@ function _io_writeWikiPage_action($data) {
}
/**
* Saves $content to $file.
*
* If the third parameter is set to true the given content
* will be appended.
*
* Uses gzip if extension is .gz
* and bz2 if extension is .bz2
* Internal function to save contents to a file.
*
* @author Andreas Gohr <andi@splitbrain.org>
*
......@@ -206,64 +214,97 @@ function _io_writeWikiPage_action($data) {
* @param bool $append
* @return bool true on success, otherwise false
*/
function io_saveFile($file,$content,$append=false){
function _io_saveFile($file, $content, $append) {
global $conf;
$mode = ($append) ? 'ab' : 'wb';
$fileexists = file_exists($file);
io_makeFileDir($file);
io_lock($file);
if(substr($file,-3) == '.gz'){
$fh = @gzopen($file,$mode.'9');
if(!$fh){
msg("Writing $file failed",-1);
io_unlock($file);
return false;
}
if(!$fh) return false;
gzwrite($fh, $content);
gzclose($fh);
}else if(substr($file,-4) == '.bz2'){
$fh = @bzopen($file,$mode{0});
if(!$fh){
msg("Writing $file failed", -1);
io_unlock($file);
return false;
if($append) {
$bzcontent = bzfile($file);
if($bzcontent === false) return false;
$content = $bzcontent.$content;
}
$fh = @bzopen($file,'w');
if(!$fh) return false;
bzwrite($fh, $content);
bzclose($fh);
}else{
$fh = @fopen($file,$mode);
if(!$fh){
msg("Writing $file failed",-1);
io_unlock($file);
return false;
}
if(!$fh) return false;
fwrite($fh, $content);
fclose($fh);
}
if(!$fileexists and !empty($conf['fperm'])) chmod($file, $conf['fperm']);
io_unlock($file);
return true;
}
/**
* Delete exact linematch for $badline from $file.
* Saves $content to $file.
*
* Be sure to include the trailing newline in $badline
* If the third parameter is set to true the given content
* will be appended.
*
* Uses gzip if extension is .gz
* and bz2 if extension is .bz2
*
* 2005-10-14 : added regex option -- Christopher Smith <chris@jalakai.co.uk>
* @author Andreas Gohr <andi@splitbrain.org>
*
* @author Steven Danz <steven-danz@kc.rr.com>
* @param string $file filename path to file
* @param string $content
* @param bool $append
* @return bool true on success, otherwise false
*/
function io_saveFile($file, $content, $append=false) {
io_makeFileDir($file);
io_lock($file);
if(!_io_saveFile($file, $content, $append)) {
msg("Writing $file failed",-1);
io_unlock($file);
return false;
}
io_unlock($file);
return true;
}
/**
* Replace one or more occurrences of a line in a file.
*
* @param string $file filename
* @param string $badline exact linematch to remove
* @param bool $regex use regexp?
* The default, when $maxlines is 0 is to delete all matching lines then append a single line.
* A regex that matches any part of the line will remove the entire line in this mode.
* Captures in $newline are not available.
*
* Otherwise each line is matched and replaced individually, up to the first $maxlines lines
* or all lines if $maxlines is -1. If $regex is true then captures can be used in $newline.
*
* Be sure to include the trailing newline in $oldline when replacing entire lines.
*
* Uses gzip if extension is .gz
* and bz2 if extension is .bz2
*
* @author Steven Danz <steven-danz@kc.rr.com>
* @author Christopher Smith <chris@jalakai.co.uk>
* @author Patrick Brown <ptbrown@whoopdedo.org>
*
* @param string $file filename
* @param string $oldline exact linematch to remove
* @param string $newline new line to insert
* @param bool $regex use regexp?
* @param int $maxlines number of occurrences of the line to replace
* @return bool true on success
*/
function io_deleteFromFile($file,$badline,$regex=false){
function io_replaceInFile($file, $oldline, $newline, $regex=false, $maxlines=0) {
if ((string)$oldline === '') {
trigger_error('$oldline parameter cannot be empty in io_replaceInFile()', E_USER_WARNING);
return false;
}
if (!file_exists($file)) return true;
io_lock($file);
......@@ -271,41 +312,40 @@ function io_deleteFromFile($file,$badline,$regex=false){
// load into array
if(substr($file,-3) == '.gz'){
$lines = gzfile($file);
}else if(substr($file,-4) == '.bz2'){
$lines = bzfile($file, true);
}else{
$lines = file($file);
}
// remove all matching lines
if ($regex) {
$lines = preg_grep($badline,$lines,PREG_GREP_INVERT);
} else {
$pos = array_search($badline,$lines); //return null or false if not found
while(is_int($pos)){
unset($lines[$pos]);
$pos = array_search($badline,$lines);
// make non-regexes into regexes
$pattern = $regex ? $oldline : '/^'.preg_quote($oldline,'/').'$/';
$replace = $regex ? $newline : addcslashes($newline, '\$');
// remove matching lines
if ($maxlines > 0) {
$count = 0;
$matched = 0;
while (($count < $maxlines) && (list($i,$line) = each($lines))) {
// $matched will be set to 0|1 depending on whether pattern is matched and line replaced
$lines[$i] = preg_replace($pattern, $replace, $line, -1, $matched);
if ($matched) $count++;
}
} else if ($maxlines == 0) {
$lines = preg_grep($pattern, $lines, PREG_GREP_INVERT);
if ((string)$newline !== ''){
$lines[] = $newline;
}
} else {
$lines = preg_replace($pattern, $replace, $lines);
}
if(count($lines)){
$content = join('',$lines);
if(substr($file,-3) == '.gz'){
$fh = @gzopen($file,'wb9');
if(!$fh){
msg("Removing content from $file failed",-1);
io_unlock($file);
return false;
}
gzwrite($fh, $content);
gzclose($fh);
}else{
$fh = @fopen($file,'wb');
if(!$fh){
msg("Removing content from $file failed",-1);
io_unlock($file);
return false;
}
fwrite($fh, $content);
fclose($fh);
if(!_io_saveFile($file, join('',$lines), false)) {
msg("Removing content from $file failed",-1);
io_unlock($file);
return false;
}
}else{
@unlink($file);
......@@ -315,6 +355,22 @@ function io_deleteFromFile($file,$badline,$regex=false){
return true;
}
/**
* Delete lines that match $badline from $file.
*
* Be sure to include the trailing newline in $badline
*
* @author Patrick Brown <ptbrown@whoopdedo.org>
*
* @param string $file filename
* @param string $badline exact linematch to remove
* @param bool $regex use regexp?
* @return bool true on success
*/
function io_deleteFromFile($file,$badline,$regex=false){
return io_replaceInFile($file,$badline,null,$regex,0);
}
/**
* Tries to lock a file
*
......
......@@ -682,7 +682,6 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
*/
function _acl_add($acl_scope, $acl_user, $acl_level){
global $config_cascade;
$acl_config = file_get_contents($config_cascade['acl']['default']);
$acl_user = auth_nameencode($acl_user,true);
// max level for pagenames is edit
......@@ -692,9 +691,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
$new_acl = "$acl_scope\t$acl_user\t$acl_level\n";
$new_config = $acl_config.$new_acl;
return io_saveFile($config_cascade['acl']['default'], $new_config);
return io_saveFile($config_cascade['acl']['default'], $new_acl, true);
}
/**
......@@ -704,15 +701,11 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin {
*/
function _acl_del($acl_scope, $acl_user){
global $config_cascade;
$acl_config = file($config_cascade['acl']['default']);
$acl_user = auth_nameencode($acl_user,true);
$acl_pattern = '^'.preg_quote($acl_scope,'/').'[ \t]+'.$acl_user.'[ \t]+[0-8].*$';
// save all non!-matching
$new_config = preg_grep("/$acl_pattern/", $acl_config, PREG_GREP_INVERT);
return io_saveFile($config_cascade['acl']['default'], join('',$new_config));
return io_deleteFromFile($config_cascade['acl']['default'], "/$acl_pattern/", true);
}
/**
......
......@@ -188,15 +188,9 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin {
$userline = $this->_createUserLine($newuser, $userinfo['pass'], $userinfo['name'], $userinfo['mail'], $userinfo['grps']);
if(!$this->deleteUsers(array($user))) {
msg($this->getLang('writefail'), -1);
return false;
}
if(!io_saveFile($config_cascade['plainauth.users']['default'], $userline, true)) {
msg('There was an error modifying your user data. You should register again.', -1);
// FIXME, user has been deleted but not recreated, should force a logout and redirect to login page
// Should replace the delete/save hybrid modify with an atomic io_replaceInFile
if(!io_replaceInFile($config_cascade['plainauth.users']['default'], '/^'.$user.':/', $userline, true)) {
msg('There was an error modifying your user data. You may need to register again.', -1);
// FIXME, io functions should be fail-safe so existing data isn't lost
$ACT = 'register';
return false;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment