From 369075828e13e37a65a2f8062a74e89f98dd3fac Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Wed, 6 May 2015 18:21:17 -0400 Subject: Append to BZip2 files. Unit tests for writing files. --- _test/tests/inc/io_deletefromfile.test.php | 42 +++++++++++++++++++++++++ _test/tests/inc/io_savefile.test.php | 49 ++++++++++++++++++++++++++++++ inc/io.php | 11 ++++++- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 _test/tests/inc/io_deletefromfile.test.php create mode 100644 _test/tests/inc/io_savefile.test.php diff --git a/_test/tests/inc/io_deletefromfile.test.php b/_test/tests/inc/io_deletefromfile.test.php new file mode 100644 index 000000000..361c82214 --- /dev/null +++ b/_test/tests/inc/io_deletefromfile.test.php @@ -0,0 +1,42 @@ +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\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)); + } + + function test_delete(){ + $this->_write(TMP_DIR.'/test.txt'); + } + +// /** +// * @depends test_ext_zlib +// */ +// function test_gzwrite(){ +// } + +} diff --git a/_test/tests/inc/io_savefile.test.php b/_test/tests/inc/io_savefile.test.php new file mode 100644 index 000000000..4a4d4671d --- /dev/null +++ b/_test/tests/inc/io_savefile.test.php @@ -0,0 +1,49 @@ +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'); + } + +} diff --git a/inc/io.php b/inc/io.php index 0636a4b62..8846b7e56 100644 --- a/inc/io.php +++ b/inc/io.php @@ -223,7 +223,16 @@ function io_saveFile($file,$content,$append=false){ gzwrite($fh, $content); gzclose($fh); }else if(substr($file,-4) == '.bz2'){ - $fh = @bzopen($file,$mode{0}); + if($append) { + $bzcontent = bzfile($file); + if($bzcontent === false) { + msg("Writing $file failed", -1); + io_unlock($file); + return false; + } + $content = $bzcontent.$content; + } + $fh = @bzopen($file,'w'); if(!$fh){ msg("Writing $file failed", -1); io_unlock($file); -- cgit v1.2.3 From cfb71e37ca8859c4ad9a1db73de0a293ffc7a902 Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Wed, 6 May 2015 23:31:39 -0400 Subject: Deleting lines works with BZ2 files. --- _test/tests/inc/io_deletefromfile.test.php | 18 ++++++++++++----- _test/tests/inc/io_readfile.test.php | 7 ++++++- _test/tests/inc/io_readfile/large.txt.bz2 | Bin 0 -> 47 bytes _test/tests/inc/io_readfile/long.txt.bz2 | Bin 0 -> 53 bytes inc/io.php | 31 ++++++++++++++++++++++++++--- 5 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 _test/tests/inc/io_readfile/large.txt.bz2 create mode 100644 _test/tests/inc/io_readfile/long.txt.bz2 diff --git a/_test/tests/inc/io_deletefromfile.test.php b/_test/tests/inc/io_deletefromfile.test.php index 361c82214..63951f548 100644 --- a/_test/tests/inc/io_deletefromfile.test.php +++ b/_test/tests/inc/io_deletefromfile.test.php @@ -33,10 +33,18 @@ class io_deletefromfile_test extends DokuWikiTest { $this->_write(TMP_DIR.'/test.txt'); } -// /** -// * @depends test_ext_zlib -// */ -// function test_gzwrite(){ -// } + /** + * @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'); + } } diff --git a/_test/tests/inc/io_readfile.test.php b/_test/tests/inc/io_readfile.test.php index e3e90cd8d..700c1902b 100644 --- a/_test/tests/inc/io_readfile.test.php +++ b/_test/tests/inc/io_readfile.test.php @@ -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 +} diff --git a/_test/tests/inc/io_readfile/large.txt.bz2 b/_test/tests/inc/io_readfile/large.txt.bz2 new file mode 100644 index 000000000..3135435f8 Binary files /dev/null and b/_test/tests/inc/io_readfile/large.txt.bz2 differ diff --git a/_test/tests/inc/io_readfile/long.txt.bz2 b/_test/tests/inc/io_readfile/long.txt.bz2 new file mode 100644 index 000000000..fb40759e6 Binary files /dev/null and b/_test/tests/inc/io_readfile/long.txt.bz2 differ diff --git a/inc/io.php b/inc/io.php index 8846b7e56..b8a77b730 100644 --- a/inc/io.php +++ b/inc/io.php @@ -127,22 +127,36 @@ function io_readFile($file,$clean=true){ * @author Andreas Gohr * * @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; } @@ -280,6 +294,8 @@ 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); } @@ -306,6 +322,15 @@ function io_deleteFromFile($file,$badline,$regex=false){ } gzwrite($fh, $content); gzclose($fh); + }else if(substr($file,-4) == '.bz2'){ + $fh = @bzopen($file,'w'); + if(!$fh){ + msg("Removing content from $file failed",-1); + io_unlock($file); + return false; + } + bzwrite($fh, $content); + bzclose($fh); }else{ $fh = @fopen($file,'wb'); if(!$fh){ -- cgit v1.2.3 From 1bd6bbdebc26f9cd916f8f287cd2cabc07bee8d1 Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Thu, 7 May 2015 01:37:11 -0400 Subject: Add io_replaceInFile --- inc/io.php | 158 +++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 86 insertions(+), 72 deletions(-) diff --git a/inc/io.php b/inc/io.php index b8a77b730..11a3fa77f 100644 --- a/inc/io.php +++ b/inc/io.php @@ -205,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 * @@ -220,73 +214,90 @@ 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'){ if($append) { $bzcontent = bzfile($file); - if($bzcontent === false) { - msg("Writing $file failed", -1); - io_unlock($file); - return false; - } + if($bzcontent === false) return false; $content = $bzcontent.$content; } $fh = @bzopen($file,'w'); - if(!$fh){ - msg("Writing $file failed", -1); - io_unlock($file); - return false; - } + 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 + * @author Andreas Gohr * - * @author Steven Danz + * @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 matches then append a single line. + * If $maxlines is -1, then every $oldline will be replaced with $newline, and $regex is true + * then preg captures are used. If $maxlines is greater than 0 then the first $maxlines + * matches are replaced with $newline. + * + * Be sure to include the trailing newline in $oldline + * + * Uses gzip if extension is .gz + * and bz2 if extension is .bz2 + * + * @author Steven Danz + * @author Christopher Smith + * @author Patrick Brown + * + * @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 (!file_exists($file)) return true; io_lock($file); @@ -302,44 +313,31 @@ function io_deleteFromFile($file,$badline,$regex=false){ // remove all matching lines if ($regex) { - $lines = preg_grep($badline,$lines,PREG_GREP_INVERT); + if($maxlines == 0) { + $lines = preg_grep($oldline, $lines, PREG_GREP_INVERT); + } else { + $lines = preg_replace($oldline, $newline, $lines, $maxlines); + } } else { - $pos = array_search($badline,$lines); //return null or false if not found + $count = 0; + $replaceline = $maxlines == 0 ? '' : $newline; + $pos = array_search($oldline,$lines); //return null or false if not found while(is_int($pos)){ - unset($lines[$pos]); - $pos = array_search($badline,$lines); + $lines[$pos] = $replaceline; + if($maxlines > 0 && ++$count >= $maxlines) break; + $pos = array_search($oldline,$lines); } } + if($maxlines == 0 && ((string)$newline) !== '') { + $lines[] = $newline; + } + 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 if(substr($file,-4) == '.bz2'){ - $fh = @bzopen($file,'w'); - if(!$fh){ - msg("Removing content from $file failed",-1); - io_unlock($file); - return false; - } - bzwrite($fh, $content); - bzclose($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); @@ -349,6 +347,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 + * + * @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 * -- cgit v1.2.3 From 699e3c4900f2d6cc860a3587a05798cd23b7944d Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Thu, 7 May 2015 14:27:00 -0400 Subject: Use io_replaceInFile for updating auth --- lib/plugins/acl/admin.php | 11 ++--------- lib/plugins/authplain/auth.php | 11 +++-------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/lib/plugins/acl/admin.php b/lib/plugins/acl/admin.php index 814bbfe9c..2dfdbbda5 100644 --- a/lib/plugins/acl/admin.php +++ b/lib/plugins/acl/admin.php @@ -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_config, 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); } /** diff --git a/lib/plugins/authplain/auth.php b/lib/plugins/authplain/auth.php index b31c02fc8..35cf7a802 100644 --- a/lib/plugins/authplain/auth.php +++ b/lib/plugins/authplain/auth.php @@ -185,14 +185,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('Unable to modify user data. Please inform the Wiki-Admin', -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 + 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; } -- cgit v1.2.3 From 6c0002048504e43b399abece0668afa2b5c87a07 Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Fri, 8 May 2015 17:11:44 -0400 Subject: Limit number of lines to replace. --- _test/tests/inc/io_replaceinfile.test.php | 58 +++++++++++++++++++++++++++++++ inc/io.php | 12 +++++-- 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 _test/tests/inc/io_replaceinfile.test.php diff --git a/_test/tests/inc/io_replaceinfile.test.php b/_test/tests/inc/io_replaceinfile.test.php new file mode 100644 index 000000000..98f21868f --- /dev/null +++ b/_test/tests/inc/io_replaceinfile.test.php @@ -0,0 +1,58 @@ +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\012Delete\012Delete\012Delete01\012Delete02\012Delete\012DeleteX\012Test\012"; + io_saveFile($file, $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'); + } + +} diff --git a/inc/io.php b/inc/io.php index 11a3fa77f..dbb42114b 100644 --- a/inc/io.php +++ b/inc/io.php @@ -313,10 +313,16 @@ function io_replaceInFile($file, $oldline, $newline, $regex=false, $maxlines=0) // remove all matching lines if ($regex) { - if($maxlines == 0) { - $lines = preg_grep($oldline, $lines, PREG_GREP_INVERT); + if($maxlines > 0) { + $matches = preg_grep($oldline, $lines); + $count = 0; + foreach($matches as $ix=>$m) { + $lines[$ix] = preg_replace($oldline, $newline, $m); + if(++$count >= $maxlines) break; + } } else { - $lines = preg_replace($oldline, $newline, $lines, $maxlines); + $lines = ($maxlines == 0) ? preg_grep($oldline, $lines, PREG_GREP_INVERT) + : preg_replace($oldline, $newline, $lines, $maxlines); } } else { $count = 0; -- cgit v1.2.3 From 0c26fb18171bc6264c1b0b2dbdfddc34de5d579e Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Fri, 8 May 2015 17:13:32 -0400 Subject: Don't run redundant tests io_deleteFromFile is a special case of io_replaceInFile. Since the io_replace tests check compressed files, it doesn't have to be done in the io_delete test. --- _test/tests/inc/io_deletefromfile.test.php | 39 ++---------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/_test/tests/inc/io_deletefromfile.test.php b/_test/tests/inc/io_deletefromfile.test.php index 63951f548..e86150aac 100644 --- a/_test/tests/inc/io_deletefromfile.test.php +++ b/_test/tests/inc/io_deletefromfile.test.php @@ -2,25 +2,8 @@ class io_deletefromfile_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){ + 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")); @@ -29,22 +12,4 @@ class io_deletefromfile_test extends DokuWikiTest { $this->assertEquals("The\012DeleteX\012Test\012", io_readFile($file)); } - function test_delete(){ - $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'); - } - } -- cgit v1.2.3 From 2b71c2eece46d1e977fa596ed57b74e04a9aaf6b Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Fri, 8 May 2015 17:57:02 -0400 Subject: Fix variable name typo --- lib/plugins/acl/admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/acl/admin.php b/lib/plugins/acl/admin.php index 2dfdbbda5..cdd96be56 100644 --- a/lib/plugins/acl/admin.php +++ b/lib/plugins/acl/admin.php @@ -691,7 +691,7 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { $new_acl = "$acl_scope\t$acl_user\t$acl_level\n"; - return io_saveFile($config_cascade['acl']['default'], $new_config, true); + return io_saveFile($config_cascade['acl']['default'], $new_acl, true); } /** -- cgit v1.2.3 From 9a734b7aaba1445e06c1ccb95e59f54e01688d45 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Thu, 28 May 2015 16:37:32 +0100 Subject: Refactor code to make it simpler. The changes should also: - fix unlikely edge case when replacement line is the same as the old line (would have resulted in timeout) - reduce memory footprint - avoid applying string search beyond maxlines replacement limit --- _test/tests/inc/io_replaceinfile.test.php | 28 +++++++++++++++++++++++-- inc/io.php | 35 +++++++++++++------------------ 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/_test/tests/inc/io_replaceinfile.test.php b/_test/tests/inc/io_replaceinfile.test.php index 98f21868f..c2dbc0dd4 100644 --- a/_test/tests/inc/io_replaceinfile.test.php +++ b/_test/tests/inc/io_replaceinfile.test.php @@ -2,6 +2,8 @@ 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 */ @@ -21,8 +23,8 @@ class io_replaceinfile_test extends DokuWikiTest { } function _write($file){ - $contents = "The\012Delete\012Delete\012Delete01\012Delete02\012Delete\012DeleteX\012Test\012"; - io_saveFile($file, $contents); + + 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)); @@ -41,6 +43,7 @@ class io_replaceinfile_test extends DokuWikiTest { $this->_write(TMP_DIR.'/test.txt'); } + /** * @depends test_ext_zlib */ @@ -55,4 +58,25 @@ class io_replaceinfile_test extends DokuWikiTest { $this->_write(TMP_DIR.'/test.txt.bz2'); } + /** + * + */ + function test_edgecase1() + { + $file = TMP_DIR . '/test.txt'; + // Replace all, no regex, backreference like construct in replacement line + 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"); + } + /** + * @small + */ + function test_edgecase2() { + $file = TMP_DIR.'/test.txt'; + // Replace all, no regex, replacement line == search line + 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"); + } } diff --git a/inc/io.php b/inc/io.php index dbb42114b..51ca2ea14 100644 --- a/inc/io.php +++ b/inc/io.php @@ -311,28 +311,23 @@ function io_replaceInFile($file, $oldline, $newline, $regex=false, $maxlines=0) $lines = file($file); } - // remove all matching lines - if ($regex) { - if($maxlines > 0) { - $matches = preg_grep($oldline, $lines); - $count = 0; - foreach($matches as $ix=>$m) { - $lines[$ix] = preg_replace($oldline, $newline, $m); - if(++$count >= $maxlines) break; - } - } else { - $lines = ($maxlines == 0) ? preg_grep($oldline, $lines, PREG_GREP_INVERT) - : preg_replace($oldline, $newline, $lines, $maxlines); - } - } else { + // make non-regexes into regexes + $pattern = $regex ? $oldline : '/'.preg_quote($oldline,'/').'/'; + $replace = $regex ? $newline : addcslashes($newline, '\$'); + + // remove matching lines + if ($maxlines > 0) { $count = 0; - $replaceline = $maxlines == 0 ? '' : $newline; - $pos = array_search($oldline,$lines); //return null or false if not found - while(is_int($pos)){ - $lines[$pos] = $replaceline; - if($maxlines > 0 && ++$count >= $maxlines) break; - $pos = array_search($oldline,$lines); + $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 { + $lines = ($maxlines == 0) ? + preg_grep($pattern, $lines, PREG_GREP_INVERT) : + preg_replace($pattern, $replace, $lines); } if($maxlines == 0 && ((string)$newline) !== '') { -- cgit v1.2.3 From 3dfe7d64760baa568018c1d6d311c26d1a2da098 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 29 May 2015 16:52:05 +0100 Subject: add anchors when constructing pattern from a non-regex oldline --- _test/tests/inc/io_replaceinfile.test.php | 13 +++++++++++++ inc/io.php | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/_test/tests/inc/io_replaceinfile.test.php b/_test/tests/inc/io_replaceinfile.test.php index c2dbc0dd4..de7301fa1 100644 --- a/_test/tests/inc/io_replaceinfile.test.php +++ b/_test/tests/inc/io_replaceinfile.test.php @@ -79,4 +79,17 @@ class io_replaceinfile_test extends DokuWikiTest { $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"); } + + /** + * + */ + function test_edgecase3() + { + $file = TMP_DIR . '/test.txt'; + $contents = "The\012Delete\01201Delete\01202Delete\012Test\012"; + // Replace all, no regex, oldline exactly matches one line; matches part of other lines - only the exact match should be replaced + 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"); + } } diff --git a/inc/io.php b/inc/io.php index 51ca2ea14..c559feb17 100644 --- a/inc/io.php +++ b/inc/io.php @@ -312,7 +312,7 @@ function io_replaceInFile($file, $oldline, $newline, $regex=false, $maxlines=0) } // make non-regexes into regexes - $pattern = $regex ? $oldline : '/'.preg_quote($oldline,'/').'/'; + $pattern = $regex ? $oldline : '/^'.preg_quote($oldline,'/').'$/'; $replace = $regex ? $newline : addcslashes($newline, '\$'); // remove matching lines -- cgit v1.2.3 From e12c5ac781d560502d478775502df70cd80472de Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 29 May 2015 16:55:23 +0100 Subject: Minor Refactoring - put test comments in more appropriate spot - move appending replacement line alongside its search/delete code --- _test/tests/inc/io_replaceinfile.test.php | 12 +++++++----- inc/io.php | 14 +++++++------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/_test/tests/inc/io_replaceinfile.test.php b/_test/tests/inc/io_replaceinfile.test.php index de7301fa1..597138a20 100644 --- a/_test/tests/inc/io_replaceinfile.test.php +++ b/_test/tests/inc/io_replaceinfile.test.php @@ -59,35 +59,37 @@ class io_replaceinfile_test extends DokuWikiTest { } /** - * + * 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'; - // Replace all, no regex, backreference like construct in replacement line + 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'; - // Replace all, no regex, replacement line == search line + 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"; - // Replace all, no regex, oldline exactly matches one line; matches part of other lines - only the exact match should be replaced + 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"); diff --git a/inc/io.php b/inc/io.php index c559feb17..4c7fb094f 100644 --- a/inc/io.php +++ b/inc/io.php @@ -324,14 +324,14 @@ function io_replaceInFile($file, $oldline, $newline, $regex=false, $maxlines=0) $lines[$i] = preg_replace($pattern, $replace, $line, -1, $matched); if ($matched) $count++; } - } else { - $lines = ($maxlines == 0) ? - preg_grep($pattern, $lines, PREG_GREP_INVERT) : - preg_replace($pattern, $replace, $lines); - } + } else if ($maxlines == 0) { + $lines = preg_grep($pattern, $lines, PREG_GREP_INVERT); - if($maxlines == 0 && ((string)$newline) !== '') { - $lines[] = $newline; + if ((string)$newline !== ''){ + $lines[] = $newline; + } + } else { + $lines = preg_replace($pattern, $replace, $lines); } if(count($lines)){ -- cgit v1.2.3 From dc4a4eb00d67d7d28fae137437900220920577d4 Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Fri, 29 May 2015 15:38:43 -0400 Subject: Abort io_replaceInLine when the search parameter is empty --- _test/tests/inc/io_replaceinfile.test.php | 11 +++++++++++ inc/io.php | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/_test/tests/inc/io_replaceinfile.test.php b/_test/tests/inc/io_replaceinfile.test.php index 597138a20..452ed7401 100644 --- a/_test/tests/inc/io_replaceinfile.test.php +++ b/_test/tests/inc/io_replaceinfile.test.php @@ -94,4 +94,15 @@ class io_replaceinfile_test extends DokuWikiTest { $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)); + } } diff --git a/inc/io.php b/inc/io.php index 4c7fb094f..6d3c20047 100644 --- a/inc/io.php +++ b/inc/io.php @@ -298,6 +298,11 @@ function io_saveFile($file, $content, $append=false) { * @return bool true on success */ 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); -- cgit v1.2.3 From d93ba631117932f06b44535a9d6256cc8e9c4b90 Mon Sep 17 00:00:00 2001 From: Patrick Brown Date: Fri, 29 May 2015 15:46:19 -0400 Subject: Rephrase description of io_replaceInFiles to be clarify use of regex --- inc/io.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/inc/io.php b/inc/io.php index 6d3c20047..9be648824 100644 --- a/inc/io.php +++ b/inc/io.php @@ -276,12 +276,14 @@ function io_saveFile($file, $content, $append=false) { /** * Replace one or more occurrences of a line in a file. * - * The default, when $maxlines is 0 is to delete all matches then append a single line. - * If $maxlines is -1, then every $oldline will be replaced with $newline, and $regex is true - * then preg captures are used. If $maxlines is greater than 0 then the first $maxlines - * matches are replaced with $newline. + * 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. * - * Be sure to include the trailing newline in $oldline + * 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 -- cgit v1.2.3