Файл: apwa/includes/editor.php
Строк: 1741
<?php
/**
*
* @package automod
* @version $Id$
* @copyright (c) 2008 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*
*/
/**
*/
if (!defined('IN_PHPBB'))
{
exit;
}
// Constant Defines for actions
//define('AFTER', 1);
//define('BEFORE', 2);
/**
* Editor Class
* Runs through file sequential, ie new finds must come after previous finds
* Handles placing the files after being edited
* @package automod
* @todo: implement some string checkin, way too much can go wild here
*/
class editor
{
/**
* Holds contents of file
*/
var $file_contents = '';
/**
* Holds the filename of the currently open file
*/
var $open_filename = '';
/**
* Full action array with complete finds
*/
var $mod_actions = array();
/**
* One of the three constants defined in functions_mods.php
*/
var $write_method = 0;
/**
* Keeps finds sequential, plus loop optimization
*/
var $start_index = 0;
/*
* Keeps inline find sequential
*/
var $last_string_offset = 0;
/*
* Only apply string offset to the line to which it belongs
*/
var $last_inline_ary_offset = 0;
/**
* Time when MOD was installed
*/
var $install_time = 0;
/**
* Only used when board has templates stored in the database
*/
var $template_id = 0;
/**
* Store the current action & most recent action to aid the uninstall building process
*/
var $last_action = array();
var $curr_action = array();
/**
* Constructor method
* This is not called directly in AutoMOD
*/
function editor()
{
}
/**
* Make all line endings the same - UNIX
*/
function normalize($string)
{
$string = str_replace(array("rn", "r"), "n", $string);
return $string;
}
/**
* Open a file with IO, for processing
*
* @param string $filename - relative path from phpBB Root to the file to open
* e.g. viewtopic.php, styles/prosilver/templates/index_body.html
*/
function open_file($filename, $backup_path = '')
{
global $phpbb_root_path, $db, $user;
$this->file_contents = @file($phpbb_root_path . $filename);
if ($this->file_contents === false)
{
return $user->lang['FILE_EMPTY'];
}
$this->file_contents = $this->normalize($this->file_contents);
// Check for file contents in the database if this is a template file
// this will overwrite the @file call if it exists in the DB.
if (strpos($filename, 'template/') !== false)
{
// grab template name and filename
//preg_match('#styles/([a-z0-9_]+)/template/([a-z0-9_]+.[a-z]+)#i', $filename, $match);
preg_match('#styles/([a-z0-9_-]+)/template/([a-z0-9_-]+.[a-z]+)#i', $filename, $match);
$sql = 'SELECT d.template_data, d.template_id
FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' d, ' . STYLES_TEMPLATE_TABLE . " t
WHERE d.template_filename = '" . $db->sql_escape($match[2]) . "'
AND t.template_id = d.template_id
AND t.template_storedb = 1
AND t.template_name = '" . $db->sql_escape($match[1]) . "'";
$result = $db->sql_query($sql);
if ($row = $db->sql_fetchrow($result))
{
$this->file_contents = explode("n", $this->normalize($row['template_data']));
// emulate the behavior of file()
$lines = sizeof($this->file_contents);
for ($i = 0; $i < $lines; $i++)
{
$this->file_contents[$i] .= "n";
}
$this->template_id = $row['template_id'];
}
else
{
$this->template_id = 0;
}
}
/*
* If the file does not exist, or is empty, die.
* Non existant files cannot be edited, and empty files will have no
* finds
*/
if (!sizeof($this->file_contents))
{
global $user;
trigger_error(sprintf($user->lang['MOD_OPEN_FILE_FAIL'], "$phpbb_root_path$filename"), E_USER_WARNING);
}
$this->start_index = 0;
$this->open_filename = $filename;
// Should we backup this file?
if ($backup_path)
{
$this->backup_file($backup_path);
}
}
/**
* Checks if a find is present
* Keep in mind partial finds and multi-line finds
*
* @param string $find - string to find
* @return mixed : array with position information if $find is found; false otherwise
*/
function find($find)
{
if ($find == '')
{
// Can't find a empty string,
return(false);
}
$find_success = 0;
$find = $this->normalize($find);
$find_ary = explode("n", rtrim($find, "n"));
$total_lines = sizeof($this->file_contents);
$find_lines = sizeof($find_ary);
$mode = array('', 'trim');
foreach ($mode as $function)
{
// we process the file sequentially ... so we keep track of indices
for ($i = $this->start_index; $i < $total_lines; $i++)
{
for ($j = 0; $j < $find_lines; $j++)
{
if ($function)
{
$find_ary[$j] = $function($find_ary[$j]);
}
// if we've reached the EOF, the find failed.
if (!isset($this->file_contents[$i + $j]))
{
return false;
}
if (!trim($find_ary[$j]))
{
// line is blank. Assume we can find a blank line, and continue on
$find_success += 1;
}
// using $this->file_contents[$i + $j] to keep the array pointer where I want it
// if the first line of the find (index 0) is being looked at, $i + $j = $i.
// if $j is > 0, we look at the next line of the file being inspected
// hopefully, this is a decent performer.
else if (strpos($this->file_contents[$i + $j], $find_ary[$j]) !== false)
{
// we found this part of the find
$find_success += 1;
}
// we might have an increment operator, which requires a regular expression match
else if (strpos($find_ary[$j], '{%:') !== false)
{
$regex = preg_replace('#{%:(d+)}#', '(d+)', $find_ary[$j]);
if (preg_match('#' . $regex . '#is', $this->file_contents[$i + $j]))
{
$find_success += 1;
}
else
{
$find_success = 0;
}
}
else
{
// the find failed. Reset $find_success
$find_success = 0;
// skip to next iteration of outer loop, that is, skip to the next line
break;
}
if ($find_success == $find_lines)
{
// we found the proper number of lines
$this->start_index = $i;
// return our array offsets
return array(
'start' => $i,
'end' => $i + $j,
);
}
}
}
}
// if return has not been previously invoked, the find failed.
return false;
}
/**
* This function is used to determine when an edit has ended, so we know that
* the current line will not be looked at again. This fixes some former bugs.
*/
function close_edit()
{
$this->start_index++;
$this->last_action = array();
$this->last_string_offset = 0;
}
/*
* In-line analog to close_edit(), above.
* Advance the pointer one character
*/
function close_inline_edit()
{
$this->last_string_offset++;
}
/**
* Find a string within a given line
*
* @param string $find Complete find - narrows the scope of the inline search
* @param string $inline_find - the substring to find
* @param int $start_offset - the line number where $find starts
* @param int $end_offset - the line number where $find ends
*
* @return mixed array on success or false on failure of find
*/
function inline_find($find, $inline_find, $start_offset = false, $end_offset = false)
{
if ($inline_find == '')
{
// Can't find a empty string,
return(false);
}
$find = $this->normalize($find);
if ($start_offset === false || $end_offset === false)
{
$offsets = $this->find($find);
if (!$offsets)
{
// the find failed, so no further action can occur.
return false;
}
$start_offset = $offsets['start'];
$end_offset = $offsets['end'];
unset($offsets);
}
// cast is required in case someone tries to find a number
// Often done in colspan="7" type inline operations
$inline_find = (string) $inline_find;
// similar method to find(). Just much more limited scope
for ($i = $start_offset; $i <= $end_offset; $i++)
{
if ($this->last_string_offset > 0 && ($this->last_inline_ary_offset == 0 || $this->last_inline_ary_offset == $i))
{
$string_offset = strpos(substr($this->file_contents[$i], $this->last_string_offset), $inline_find);
if ($string_offset !== false)
{
$string_offset += $this->last_string_offset;
}
}
else
{
$string_offset = strpos($this->file_contents[$i], $inline_find);
}
if ($string_offset !== false)
{
$this->last_string_offset = $string_offset;
$this->last_inline_ary_offset = $i;
// if we find something, return the line number, string offset, and find length
return array(
'array_offset' => $i,
'string_offset' => $string_offset,
'find_length' => strlen($inline_find),
);
}
}
// if the previous failed, trim() the find and try again
for ($i = $start_offset; $i <= $end_offset; $i++)
{
$inline_find = trim($inline_find);
if ($this->last_string_offset > 0 && ($this->last_inline_ary_offset == 0 || $this->last_inline_ary_offset == $i))
{
$string_offset = strpos(substr($this->file_contents[$i], $this->last_string_offset), $inline_find);
if ($string_offset !== false)
{
$string_offset += $this->last_string_offset;
}
}
else
{
$string_offset = strpos($this->file_contents[$i], $inline_find);
}
if ($string_offset !== false)
{
$this->last_string_offset = $string_offset;
// if we find something, return the line number, string offset, and find length
return array(
'array_offset' => $i,
'string_offset' => $string_offset,
'find_length' => strlen($inline_find),
);
}
}
return false;
}
/**
* Add a string to the file, BEFORE/AFTER the given find string
* @param string $find - Complete find - narrows the scope of the inline search
* @param string $add - The string to be added before or after $find
* @param string $pos - BEFORE or AFTER
* @param int $start_offset - First line in the FIND
* @param int $end_offset - Last line in the FIND
*
* @return bool success or failure of add
*/
function add_string($find, $add, $pos, $start_offset = false, $end_offset = false)
{
// this seems pretty simple...throughly test
$add = $this->normalize($add);
if ($start_offset === false || $end_offset === false)
{
$offsets = $this->find($find);
if (!$offsets)
{
// the find failed, so the add cannot occur.
return false;
}
$start_offset = $offsets['start'];
$end_offset = $offsets['end'];
unset($offsets);
}
$full_find = array();
for ($i = $start_offset; $i <= $end_offset; $i++)
{
$full_find[] = $this->file_contents[$i];
}
$full_find[0] = ltrim($full_find[0], "n");
$full_find[sizeof($full_find) - 1] = rtrim($full_find[sizeof($full_find) - 1], "n");
// make sure our new lines are correct
$add = "n" . trim($add, "n") . "n";
if ($pos == 'AFTER')
{
$this->file_contents[$end_offset] = rtrim($this->file_contents[$end_offset], "n") . $add;
}
if ($pos == 'BEFORE')
{
$this->file_contents[$start_offset] = $add . ltrim($this->file_contents[$start_offset], "n");
}
$this->curr_action = func_get_args();
$this->build_uninstall(implode("", $full_find), NULL, strtolower($pos) . ' add', $add);
return true;
}
/**
* Increment (or perform other mathematical operation) on the given wildcard
* Support multiple wildcards {%:1}, {%:2} etc...
* This method is a variation on the inline find and replace methods
*
* @param string $find - Complete find - contains $inline_find
* @param string $inline_find - contains tokens to be replaced
* @param string $operation - tokens to do some math
* @param int $start_offset - First line in the FIND
* @param int $end_offset - Last line in the FIND
*
* @return bool
*/
function inc_string($find, $inline_find, $operation, $start_offset = false, $end_offset = false)
{
if ($start_offset === false || $end_offset === false)
{
$offsets = $this->find($find);
if (!$offsets)
{
// the find failed, so the add cannot occur.
return false;
}
$start_offset = $offsets['start'];
$end_offset = $offsets['end'];
unset($offsets);
}
// $inline_find is optional
if (!$inline_find)
{
$inline_find = $find;
}
// parse the MODX operator
// let's explain this regex a bit:
// - literal %: followed by a number. optional space
// - plus or minus operator. optional space
// - number to increment by. optional
preg_match('#{%:(d+)} ?([+-]) ?(d*)#', $operation, $action);
// make sure there is actually a number here
$action[2] = (isset($action[2])) ? $action[2] : '+';
$action[3] = (isset($action[3])) ? $action[3] : 1;
$matches = 0;
// $start_offset _should_ equal $end_offset, but we allow other cases
for ($i = $start_offset; $i <= $end_offset; $i++)
{
// This is intended. We turn the MODX token into something PCRE can
// understand.
$inline_find = preg_replace('#{%:(d+)}#', '(d+)', $inline_find);
if (preg_match('#' . $inline_find . '#is', $this->file_contents[$i], $find_contents))
{
// now we can do some math
// $find_contents[1] is the original number, $action[2] is the operator
$new_number = eval('return ' . ((int) $find_contents[1]) . $action[2] . ((int) $action[3]) . ';');
// now we replace
$new_contents = str_replace($find_contents[1], $new_number, $find_contents[0]);
$this->file_contents[$i] = str_replace($find_contents[0], $new_contents, $this->file_contents[$i]);
$matches += 1;
}
}
if (!$matches)
{
return false;
}
return true;
}
/**
* Replace a string - replaces the entirety of $find with $replace
*
* @param string $find - Complete find - contains $inline_find
* @param string $replace - Will replace $find
* @param int $start_offset - First line in the FIND
* @param int $end_offset - Last line in the FIND
*
* @return bool
*/
function replace_string($find, $replace, $start_offset = false, $end_offset = false)
{
$replace = $this->normalize($replace);
if ($start_offset === false || $end_offset === false)
{
$offsets = $this->find($find);
if (!$offsets)
{
return false;
}
$start_offset = $offsets['start'];
$end_offset = $offsets['end'];
unset($offsets);
}
// remove each line from the file, but add it to $full_find
$full_find = array();
for ($i = $start_offset; $i <= $end_offset; $i++)
{
$full_find[] = $this->file_contents[$i];
$this->file_contents[$i] = '';
}
$this->file_contents[$start_offset] = rtrim($replace) . "n";
$this->curr_action = func_get_args();
$this->build_uninstall(implode("", $full_find), NULL, 'replace-with', $replace);
return true;
}
/*
* Replace $inline_find with $inline_replace
* Arguments are very similar to inline_add, below
*/
function inline_replace($find, $inline_find, $inline_replace, $array_offset = false, $string_offset = false, $length = false)
{
if ($string_offset === false || $length === false)
{
// look for the inline find
$inline_offsets = $this->inline_find($find, $inline_find);
if (!$inline_offsets)
{
return false;
}
$array_offset = $inline_offsets['array_offset'];
$string_offset = $inline_offsets['string_offset'];
$length = $inline_offsets['find_length'];
unset($inline_offsets);
}
$this->file_contents[$array_offset] = substr_replace($this->file_contents[$array_offset], $inline_replace, $string_offset, $length);
$this->last_string_offset += strlen($inline_replace) - 1;
$this->curr_action = func_get_args();
// This isn't a full find, but it is the closest we can get
$this->build_uninstall($this->file_contents[$array_offset], $inline_find, 'in-line-replace', $inline_replace);
return true;
}
/**
* Adds a string inline before or after a given find
*
* @param string $find Complete find - narrows the scope of the inline search
* @param string $inline_find - the string to add before or after
* @param string $inline_add - added before or after $inline_find
* @param string $pos - 'BEFORE' or 'AFTER'
* @param int $array_offset - line number where $inline_find may be found (optional)
* @param int $string_offset - location within the line where $inline_find begins (optional)
* @param int $length - essentially strlen($inline_find) (optional)
*
* @return bool success or failure of action
*/
function inline_add($find, $inline_find, $inline_add, $pos, $array_offset = false, $string_offset = false, $length = false)
{
if ($string_offset === false || $length === false)
{
// look for the inline find
$inline_offsets = $this->inline_find($find, $inline_find);
if (!$inline_offsets)
{
return false;
}
$array_offset = $inline_offsets['array_offset'];
$string_offset = $inline_offsets['string_offset'];
$length = $inline_offsets['find_length'];
unset($inline_offsets);
}
if ($string_offset + $length > strlen($this->file_contents[$array_offset]))
{
// we have an invalid string offset. rats.
return false;
}
if ($pos == 'AFTER')
{
$this->file_contents[$array_offset] = substr_replace($this->file_contents[$array_offset], $inline_add, $string_offset + $length, 0);
$this->last_string_offset += strlen($inline_add) + $length - 1;
}
else if ($pos == 'BEFORE')
{
$this->file_contents[$array_offset] = substr_replace($this->file_contents[$array_offset], $inline_add, $string_offset, 0);
$this->last_string_offset += (strlen($inline_add) - 1);
}
$this->curr_action = func_get_args();
$this->build_uninstall($this->file_contents[$array_offset], $inline_find, 'in-line-' . strtolower($pos) . '-add', $inline_add);
return true;
}
/**
* Function to build full edits such that uninstall will work more often
*
* @param $find - The largest find we can put together -- sometimes this
* comes from the file itself, other times from the MODX file
* @param $inline_find - Subset of $find or NULL
* @param $action_type - Name of the MODX action being taken
* @param $action - The code which is being inserted into the file
* @return void
*/
function build_uninstall($find, $inline_find, $action_type, $action)
{
$find = trim($find, "n");
$inline_find = trim($inline_find, "n");
$action = trim($action, "n");
/*
* This if statement finds out if we are in the special case where
* a MOD specifies a before action and an after action on the same
* find. If this is the case, the uninstaller must see a replace
* rather than an add
*/
if (!empty($this->last_action) && $this->last_action[0] == $this->curr_action[0] &&
(($this->last_action[2] == 'AFTER' && $this->curr_action[2] == 'BEFORE')
|| ($this->last_action[2] == 'BEFORE' && $this->curr_action[2] == 'AFTER')))
{
array_pop($this->mod_actions[$this->open_filename]);
$action_type = 'REPLACE';
// Remove the add from the find -- this is an effect of the way the
// add method works, putting the new lines in the same array element
// as the find
$find = str_replace(trim($this->last_action[1]), '', $find);
if ($this->last_action[2] == 'AFTER')
{
$action = $this->curr_action[1] . "n" . $this->curr_action[0] . "n" . $this->last_action[1];
}
else // implicit if ($this->last_action[2] == 'BEFORE')
{
$action = $this->last_action[1] . "n" . $this->curr_action[0] . "n" . $this->curr_action[1];
}
}
// Build another complex array of MOD Actions
// This approach is rather memory-intensive ... it might behoove us
// to think of something else
if (!$inline_find)
{
$this->mod_actions[$this->open_filename][] = array(
$find => array(
$action_type => $action,
)
);
}
else
{
// Do we have preceding in-line-edit(s) on the same complete find or line?
if (!empty($this->last_action) && $this->last_action[0] == $this->curr_action[0])
{
$prev_inline_edits = array_pop($this->mod_actions[$this->open_filename]);
$prev_find = key($prev_inline_edits);
// Add our current in-line-edit
$prev_inline_edits[$prev_find]['in-line-edit'][] = array(
$inline_find => array(
$action_type => array($action),
),
);
// Add the new set of in-line-edit's to our MOD Actions array, w/ the updated $find
$this->mod_actions[$this->open_filename][] = array(
$find => $prev_inline_edits[$prev_find]
);
}
else
{
$this->mod_actions[$this->open_filename][] = array(
$find => array(
'in-line-edit' => array(
array(
$inline_find => array(
$action_type => array($action),
),
),
),
),
);
}
}
$this->last_action = $this->curr_action;
}
function clear_actions()
{
// free some memory
$this->mod_actions = array();
}
}
/**
* @package automod
* class editor_direct will alter files by using the local file access functions
* such as fopen and fwrite. This is typically only useful in Windows environments
* due to permissions settings.
*/
class editor_direct extends editor
{
function editor_direct()
{
$this->write_method = WRITE_DIRECT;
$this->install_time = time();
}
/**
* Copies files or complete directories
*
* @param $from string Can be a file or a directory. Will copy either the file or all files within the directory
* @param $to string Where to copy the file(s) to. If not specified then will get copied to the phpbb root directory
* @return mixed: Bool true on success, error string on failure, NULL if no action was taken
*
* NOTE: function should preferably not return in case of failure on only one file.
* The current method makes error handling difficult
*/
function copy_content($from, $to = '')
{
global $phpbb_root_path, $user, $config;
if (strpos($from, $phpbb_root_path) !== 0)
{
$from = $phpbb_root_path . $from;
}
if (strpos($to, $phpbb_root_path) !== 0)
{
$to = $phpbb_root_path . $to;
}
$files = array();
if (is_dir($from))
{
$files = find_files($from, '.*');
$dirname_check = $to;
$to_is_dir = true;
}
else if (is_file($from))
{
$files = array($from);
$dirname_check = dirname($to);
$to_is_dir = false;
}
if (empty($files))
{
return false;
}
else if (!is_dir($dirname_check) && $this->recursive_mkdir($dirname_check) === false)
{
return sprintf($user->lang['MODS_MKDIR_FAILED'], $dirname_check);
}
foreach ($files as $file)
{
if ($to_is_dir)
{
$dest = str_replace($from, $to, $file);
$dest_parent_dir = dirname($dest);
if (!file_exists($dest_parent_dir))
{
$this->recursive_mkdir($dest_parent_dir);
}
}
else
{
$dest = $to;
}
if (!@copy($file, $dest))
{
return sprintf($user->lang['MODS_COPY_FAILURE'], $dest);
}
@chmod($dest, octdec($config['am_file_perms']));
}
return true;
}
function close_file($new_filename)
{
global $phpbb_root_path, $config, $mod_installed, $mod_uninstalled, $force_install;
global $db, $user;
if (!is_dir($new_filename) && !file_exists(dirname($new_filename)))
{
if ($this->recursive_mkdir(dirname($new_filename)) === false)
{
return sprintf($user->lang['MODS_MKDIR_FAILED'], dirname($new_filename));
}
}
$file_contents = implode('', $this->file_contents);
if (file_exists($new_filename) && !is_writable($new_filename))
{
return sprintf($user->lang['WRITE_DIRECT_FAIL'], $new_filename);
}
if ($this->template_id && ($mod_installed || $mod_uninstalled || $force_install))
{
update_database_template($new_filename, $this->template_id, $file_contents, $this->install_time);
}
// If we are not looking at a file stored in the database, use local file functions
$fr = @fopen($new_filename, 'wb');
$length_written = @fwrite($fr, $file_contents);
@chmod($new_filename, octdec($config['am_file_perms']));
// This appears to be correct even with multibyte encodings. strlen and
// fwrite both return the number of bytes written, not the number of chars
if ($length_written < strlen($file_contents))
{
return sprintf($user->lang['WRITE_DIRECT_TOO_SHORT'], $new_filename);
}
if (!@fclose($fr))
{
return sprintf($user->lang['WRITE_DIRECT_FAIL'], $new_filename);
}
return true;
}
/**
* Creates a backup of the currently open file before AutoMOD makes any changes
*/
function backup_file($backup_dir)
{
return $this->close_file($backup_dir . $this->open_filename);
}
/**
* @author Michal Nazarewicz (from the php manual)
* Creates all non-existant directories in a path
* @param $path - path to create
* @param $mode - CHMOD the new dir to these permissions
* @return bool
*/
function recursive_mkdir($path, $mode = false)
{
if (!$mode)
{
global $config;
$mode = octdec($config['am_dir_perms']);
}
$dirs = explode('/', $path);
$count = sizeof($dirs);
$path = '.';
for ($i = 0; $i < $count; $i++)
{
$path .= '/' . $dirs[$i];
if (!is_dir($path))
{
@mkdir($path, $mode);
@chmod($path, $mode);
if (!is_dir($path))
{
return false;
}
}
}
return true;
}
function commit_changes($source, $destination)
{
return $this->copy_content($source, $destination);
}
function commit_changes_final($source, $destination)
{
return NULL;
}
function create_edited_root($dir)
{
return $this->recursive_mkdir($dir);
}
/**
* Removes a file or a directory (optionally recursively)
*
* @param $path string (required) - Filename/Directory path to remove
* @param $recursive bool (optional) - Recursively delete directories
* @author jasmineaura
*/
function remove($path, $recursive = false)
{
global $phpbb_root_path, $user;
if (strpos($path, $phpbb_root_path) !== 0)
{
$path = $phpbb_root_path . $path;
}
if (is_dir($path))
{
if ($recursive)
{
// recursively delete (in functions_mod.php)
return recursive_unlink($path);
}
else
{
if (check_empty_dir($path))
{
// No error message.
// If the directory is not empty it is probably not an error.
if (!rmdir($path))
{
return sprintf($user->lang['MODS_RMDIR_FAILURE'], $path);
}
}
}
}
else
{
if (!unlink($path))
{
return sprintf($user->lang['MODS_RMFILE_FAILURE'], $path);
}
}
return true;
}
}
class editor_ftp extends editor
{
var $transfer;
function editor_ftp()
{
global $config, $user;
$this->write_method = WRITE_FTP;
$this->install_time = time();
if (!class_exists('transfer'))
{
global $phpbb_root_path, $phpEx;
include($phpbb_root_path . 'includes/functions_transfer.' . $phpEx);
}
$this->transfer = new $config['ftp_method']($config['ftp_host'], $config['ftp_username'], request_var('password', ''), $config['ftp_root_path'], $config['ftp_port'], $config['ftp_timeout']);
$error = $this->transfer->open_session();
// Use the permissions settings specified in the AutoMOD configuration
$this->transfer->dir_perms = octdec($config['am_dir_perms']);
$this->transfer->file_perms = octdec($config['am_file_perms']);
if (is_string($error))
{
// FTP login failed
trigger_error(sprintf($user->lang['MODS_FTP_CONNECT_FAILURE'], $user->lang[$error]), E_USER_ERROR);
}
}
/**
* Copies files or complete directories
*
* @param $from string Can be a file or a directory. Will copy either the file or all files within the directory
* @param $to string Where to copy the file(s) to. If not specified then will get copied to the phpbb root directory
* @return mixed: Bool true on success, error string on failure, NULL if no action was taken
*
* NOTE: function should preferably not return in case of failure on only one file.
* The current method makes error handling difficult
*/
function copy_content($from, $to = '')
{
global $phpbb_root_path, $user;
// Prefix $from with $phpbb_root_path
if (strpos($from, $phpbb_root_path) !== 0)
{
$from = $phpbb_root_path . $from;
}
if (strpos($to, $phpbb_root_path) !== 0)
{
$to = $phpbb_root_path . $to;
}
$files = array();
if (is_dir($from))
{
$files = find_files($from, '.*');
$dirname_check = $to;
$to_is_dir = true;
}
else if (is_file($from))
{
$files = array($from);
$dirname_check = dirname($to);
$to_is_dir = false;
}
if (empty($files))
{
return false;
}
else if (!is_dir($dirname_check) && $this->recursive_mkdir($dirname_check) === false)
{
return sprintf($user->lang['MODS_MKDIR_FAILED'], $dirname_check);
}
foreach ($files as $file)
{
if ($to_is_dir)
{
$dest = str_replace($from, $to, $file);
$dest_parent_dir = dirname($dest);
if (!file_exists($dest_parent_dir))
{
$this->recursive_mkdir($dest_parent_dir);
}
}
else
{
$dest = $to;
}
// Per functions_transfer.php, overwrite_file() (which we'll use here) is the only
// transfer method that doesn't strip out phpbb_root_path, because it is called
// by other methods (write_file, copy_file) that already do so before calling it.
// The ftp class prepends the real (ftp) $root_path !
$dest = str_replace($phpbb_root_path, '', $dest);
if (!$this->transfer->overwrite_file($file, $dest))
{
return sprintf($user->lang['MODS_FTP_FAILURE'], $dest);
}
}
return true;
}
/**
* Write & close file
*/
function close_file($new_filename)
{
global $phpbb_root_path, $edited_root, $mod_installed, $mod_uninstalled, $force_install;
global $db, $user;
if (!is_dir($new_filename) && !file_exists(dirname($new_filename)))
{
if ($this->recursive_mkdir(dirname($new_filename)) === false)
{
return sprintf($user->lang['MODS_MKDIR_FAILED'], dirname($new_filename));
}
}
$file_contents = implode('', $this->file_contents);
if ($this->template_id && ($mod_installed || $mod_uninstalled || $force_install))
{
update_database_template($new_filename, $this->template_id, $file_contents, $this->install_time);
}
if (!$this->transfer->write_file($new_filename, $file_contents))
{
return sprintf($user->lang['MODS_FTP_FAILURE'], $new_filename);
}
return true;
}
/**
* Creates a backup of the currently open file before AutoMOD makes any changes
*/
function backup_file($backup_dir)
{
return $this->close_file($backup_dir . $this->open_filename);
}
/**
* @ignore
*/
function recursive_mkdir($path, $mode = false)
{
if ($mode)
{
$this->transfer->dir_perms = $mode;
}
return $this->transfer->make_dir($path);
}
function commit_changes($source, $destination)
{
// Move edited files back
return $this->copy_content($source, $destination);
}
function commit_changes_final($source, $destionation)
{
return NULL;
}
function create_edited_root($dir)
{
return $this->recursive_mkdir($dir);
}
/**
* Removes a file or a directory (optionally recursively)
*
* @param $path string (required) - Filename/Directory path to remove
* @param $recursive bool (optional) - Recursively delete directories
* @author jasmineaura
*/
function remove($path, $recursive = false)
{
global $phpbb_root_path, $phpEx, $user;
// No need to strip phpbb_root_path afterwards, as the transfer class
// functions - remove_dir() and delete_file() - already do that for us
if (strpos($path, $phpbb_root_path) !== 0)
{
$path = $phpbb_root_path . $path;
}
if (is_dir($path))
{
// Recursive delete:
// remove_dir() in functions_transfer.php still says "todo remove child directories?"
// It's really easy to recursively delete in ftp, but unfortunately what exposes access to
// ftp_nlist() (or NLST); "_ls", is private to the transfer class, so we can't use it yet
if ($recursive)
{
// Insurance - this should never really happen
if ($path == $phpbb_root_path || is_file("$path/common.$phpEx"))
{
return false;
}
// Get all of the files in the source directory
$files = find_files($path, '.*');
// Get all of the sub-directories in the source directory
$subdirs = find_files($path, '.*', 20, true);
// Delete all the files
foreach ($files as $file)
{
if (!$this->transfer->delete_file($file))
{
return sprintf($user->lang['MODS_RMFILE_FAILURE'], $file);
}
}
// Delete all the sub-directories, in _reverse_ order (array_pop)
for ($i=0, $cnt = count($subdirs); $i < $cnt; $i++)
{
$subdir = array_pop($subdirs);
if (!$this->transfer->remove_dir($subdir))
{
return sprintf($user->lang['MODS_RMDIR_FAILURE'], $subdir);
}
}
// Finally, delete the directory itself
if (!$this->transfer->remove_dir($path))
{
return sprintf($user->lang['MODS_RMDIR_FAILURE'], $path);
}
}
else if (!$this->transfer->remove_dir($path))
{
return sprintf($user->lang['MODS_RMDIR_FAILURE'], $path);
}
}
else if (!$this->transfer->delete_file($path))
{
return sprintf($user->lang['MODS_RMFILE_FAILURE'], $path);
}
return true;
}
}
class editor_manual extends editor
{
function editor_manual()
{
global $config, $phpbb_root_path;
$this->write_method = WRITE_MANUAL;
$this->install_time = time();
if (!class_exists('compress'))
{
global $phpEx;
include($phpbb_root_path . 'includes/functions_compress.' . $phpEx);
}
// Ugly regular expression to extract "tar" from "tar.gz" or "tar.bz2"
// Made ugly because it does nothing with "zip"
preg_match('#.(w{3}).?.*#', $config['compress_method'], $match);
$class = 'compress_' . $match[1];
$this->compress = new $class('w', $phpbb_root_path . 'store/mod_' . $this->install_time . $config['compress_method'], $config['compress_method']);
}
function copy_content($from, $to = '')
{
global $phpbb_root_path, $user;
if (strpos($from, $phpbb_root_path) !== 0)
{
$from = $phpbb_root_path . $from;
}
if (strpos($to, $phpbb_root_path) !== 0)
{
$to = $phpbb_root_path . $to;
}
// Note: phpBB's compression class does support adding a whole directory at a time.
// However, I chose not to use that function because it would not allow AutoMOD's
// error handling to work the same as for FTP & Direct methods.
$files = array();
if (is_dir($from))
{
$files = find_files($from, '.*');
$to_is_dir = true;
}
else if (is_file($from))
{
$files = array($from);
$to_is_dir = false;
}
if (empty($files))
{
return false;
}
foreach ($files as $file)
{
if ($to_is_dir)
{
$dest = str_replace($from, $to, $file);
}
else
{
$dest = $to;
}
// Replace root path with the "files/" directory that goes in the zip
$dest = str_replace($phpbb_root_path, 'files/', $dest);
if (!$this->compress->add_custom_file($file, $dest))
{
return sprintf($user->lang['WRITE_MANUAL_FAIL'], $dest);
}
}
// return true since we are now taking an action - NULL implies no action
return true;
}
/**
* Write & close file
*/
function close_file($new_filename)
{
global $phpbb_root_path, $edited_root, $mod_installed, $mod_uninstalled, $force_install;
global $db, $user;
$file_contents = implode('', $this->file_contents);
if ($this->template_id && ($mod_installed || $mod_uninstalled || $force_install))
{
update_database_template($new_filename, $this->template_id, $file_contents, $this->install_time);
}
// don't include extra dirs in zip file
$strip_position = strpos($new_filename, '_edited') + 8; // want the end of the string
$new_filename = 'files/' . substr($new_filename, $strip_position);
if (!$this->compress->add_data($file_contents, $new_filename))
{
return sprintf($user->lang['WRITE_MANUAL_FAIL'], $new_filename);
}
return true;
}
/**
* Backup is undefined when creating a compressed file.
*/
function backup_file($backup_dir)
{
return NULL;
}
function recursive_mkdir($path, $mode = 0777)
{
return NULL;
}
function commit_changes($source, $destination)
{
global $template, $user, $phpbb_admin_path;
$download_url = append_sid("{$phpbb_admin_path}index.php", 'i=mods&mode=frontend&action=download&time=' . $this->install_time);
$template->assign_vars(array(
'S_MANUAL_INSTRUCTIONS' => true,
'L_AM_MANUAL_INSTRUCTIONS' => sprintf($user->lang['AM_MANUAL_INSTRUCTIONS'], '<a href="' . $download_url . '">', '</a>'),
));
meta_refresh(3, $download_url);
$this->compress->close();
return true;
}
function commit_changes_final($source, $destination)
{
return NULL;
}
function create_edited_root($dir)
{
return NULL;
}
function remove($file, $recursive = false)
{
return NULL;
}
}
?>