path(path, true) in that instead of an xmlArray of elements, an array of xmlArray's is returned for use with foreach. string xmlArray::create_xml(string path = '.') - returns the specified path as an xml file. */ // Get the data from the file and extract it. function read_tgz_file($gzfilename, $destination, $single_file = false, $overwrite = false) { if (function_exists('file_get_contents')) { $data = @file_get_contents($gzfilename); if ($data === false) return false; } else { $fp = @fopen($gzfilename, 'rb'); if ($fp === false) return false; $data = ''; while (!feof($fp)) $data .= fread($fp, 4096); fclose($fp); } return read_tgz_data($data, $destination, $single_file, $overwrite); } // Extract tar.gz data. If destination is null, return a listing. function read_tgz_data($data, $destination, $single_file = false, $overwrite = false) { umask(0); if ($destination !== null && !file_exists($destination) && !$single_file) mkdir($destination, 0777); // No signature? if (strlen($data) < 2) return false; $id = unpack('H2a/H2b', substr($data, 0, 2)); if (strtolower($id['a'] . $id['b']) != '1f8b') { // Okay, this ain't no tar.gz, but maybe it's a zip file. if (substr($data, 0, 2) == 'PK') return read_zip_data($data, $destination, $single_file, $overwrite); else return false; } $flags = unpack('Ct/Cf', substr($data, 2, 2)); // Not deflate! if ($flags['t'] != 8) return false; $flags = $flags['f']; $offset = 10; $octdec = array('mode', 'uid', 'gid', 'size', 'mtime', 'checksum', 'type'); // "Read" the filename and comment. if ($flags & 12) { while ($flags & 8 && $data{$offset++} != "\0") $offset; while ($flags & 4 && $data{$offset++} != "\0") $offset; } $crc = unpack('Vcrc32/Visize', substr($data, strlen($data) - 8, 8)); $data = @gzinflate(substr($data, $offset, strlen($data) - 8 - $offset)); if ($crc['crc32'] != crc32($data)) return false; $blocks = strlen($data) / 512 - 1; $offset = 0; $return = array(); while ($offset < $blocks) { $header = substr($data, $offset << 9, 512); $current = unpack('a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100linkname/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155path', $header); foreach ($current as $k => $v) { if (in_array($k, $octdec)) $current[$k] = octdec(trim($v)); else $current[$k] = trim($v); } $checksum = 256; for ($i = 0; $i < 148; $i++) $checksum += ord($header{$i}); for ($i = 156; $i < 512; $i++) $checksum += ord($header{$i}); if ($current['checksum'] != $checksum) return $return; $size = ceil($current['size'] / 512); $current['data'] = substr($data, ++$offset << 9, $current['size']); $offset += $size; // Not a directory and doesn't exist already... if (substr($current['filename'], -1, 1) != '/' && !file_exists($destination . '/' . $current['filename'])) $write_this = true; // File exists... check if it is newer. elseif (substr($current['filename'], -1, 1) != '/') $write_this = $overwrite || filemtime($destination . '/' . $current['filename']) < $current['mtime']; // Folder... create. elseif ($destination !== null && !$single_file) { // Protect from accidental parent directory writing... $current['filename'] = strtr($current['filename'], array('../' => '', '/..' => '')); if (!file_exists($destination . '/' . $current['filename'])) mkdir($destination . '/' . $current['filename'], 0777); $write_this = false; } else $write_this = false; if ($write_this && $destination !== null) { if (strpos($current['filename'], '/') !== false && !$single_file) { $dirs = explode('/', $current['filename']); array_pop($dirs); $dirpath = $destination . '/'; foreach ($dirs as $dir) { if (!file_exists($dirpath . $dir)) mkdir($dirpath . $dir, 0777); $dirpath .= $dir . '/'; } } // Is this the file we're looking for? if ($single_file && ($destination == $current['filename'] || $destination == '*/' . basename($current['filename']))) return $current['data']; // If we're looking for another file, keep going. elseif ($single_file) continue; file_put_contents($destination . '/' . $current['filename'], $current['data']); } if (substr($current['filename'], -1, 1) != '/') $return[] = array( 'filename' => $current['filename'], 'size' => $current['size'], 'skipped' => false ); } if ($single_file) return false; else return $return; } // Extract zip data. If destination is null, return a listing. function read_zip_data($data, $destination, $single_file = false, $overwrite = false) { umask(0); if ($destination !== null && !file_exists($destination) && !$single_file) mkdir($destination, 0777); // Look for the PK header... if (substr($data, 0, 2) != 'PK') return false; // Find the central whosamawhatsit at the end; if there's a comment it's a pain. if (substr($data, -22, 4) == 'PK' . chr(5) . chr(6)) $p = -22; else { // Have to find where the comment begins, ugh. for ($p = -22; $p > -strlen($data); $p--) { if (substr($data, $p, 4) == 'PK' . chr(5) . chr(6)) break; } } $return = array(); // Get the basic zip file info. $zip_info = unpack('vfiles/Vsize/Voffset', substr($data, $p + 10, 10)); $p = $zip_info['offset']; for ($i = 0; $i < $zip_info['files']; $i++) { // Make sure this is a file entry... if (substr($data, $p, 4) != 'PK' . chr(1) . chr(2)) return false; // Get all the important file information. $file_info = unpack('Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', substr($data, $p + 16, 30)); $file_info['filename'] = substr($data, $p + 46, $file_info['filename_len']); // Skip all the information we don't care about anyway. $p += 46 + $file_info['filename_len'] + $file_info['extra_len'] + $file_info['comment_len']; // If this is a file, and it doesn't exist.... happy days! if (substr($file_info['filename'], -1, 1) != '/' && !file_exists($destination . '/' . $file_info['filename'])) $write_this = true; // If the file exists, we may not want to overwrite it. elseif (substr($file_info['filename'], -1, 1) != '/') $write_this = $overwrite; // This is a directory, so we're gonna want to create it. (probably...) elseif ($destination !== null && !$single_file) { // Just a little accident prevention, don't mind me. $file_info['filename'] = strtr($file_info['filename'], array('../' => '', '/..' => '')); if (!file_exists($destination . '/' . $file_info['filename'])) mkdir($destination . '/' . $file_info['filename'], 0777); $write_this = false; } else $write_this = false; // Okay! We can write this file, looks good from here... if ($write_this && $destination !== null) { if (strpos($file_info['filename'], '/') !== false && !$single_file) { // Make any parents this file may need to have for things to work out. $dirs = explode('/', $file_info['filename']); array_pop($dirs); $dirpath = $destination . '/'; foreach ($dirs as $dir) { if (!file_exists($dirpath . $dir)) mkdir($dirpath . $dir, 0777); $dirpath .= $dir . '/'; } } // Check that the data is there and does exist. if (substr($data, $file_info['offset'], 4) != 'PK' . chr(3) . chr(4)) return false; // Get the actual compressed data. $file_info['data'] = substr($data, $file_info['offset'] + 30 + $file_info['filename_len'] + $file_info['extra_len'], $file_info['compressed_size']); // Only inflate it if we need to ;). if ($file_info['compressed_size'] != $file_info['size']) $file_info['data'] = @gzinflate($file_info['data']); // If we're looking for a specific file, and this is it... ka-bam, baby. if ($single_file && ($destination == $file_info['filename'] || $destination == '*/' . basename($file_info['filename']))) return $file_info['data']; // Oh? Another file. Fine. You don't like this file, do you? I know how it is. Yeah... just go away. No, don't apologize. I know this file's just not *good enough* for you. elseif ($single_file) continue; file_put_contents($destination . '/' . $file_info['filename'], $file_info['data']); } if (substr($file_info['filename'], -1, 1) != '/') $return[] = array( 'filename' => $file_info['filename'], 'size' => $file_info['size'], 'skipped' => false ); } if ($single_file) return false; else return $return; } // Checks the existence of a remote file since file_exists() does not do remote. function url_exists($url) { $a_url = parse_url($url); if (!isset($a_url['scheme'])) return false; // Attempt to connect... $temp = ''; $fid = fsockopen($a_url['host'], !isset($a_url['port']) ? 80 : $a_url['port'], $temp, $temp, 8); if (!$fid) return false; fputs($fid, 'HEAD ' . $a_url['path'] . " HTTP/1.0\r\nHost: " . $a_url['host'] . "\r\n\r\n"); $head = fread($fid, 1024); fclose($fid); return preg_match('~^HTTP/.+\s+200~i', $head) == 1; } // Load the installed packages. function loadInstalledPackages() { global $boarddir; $installed_mods = file($boarddir . '/Packages/installed.list'); $installed = array(); for ($i = 0, $n = count($installed_mods); $i < $n; $i++) { // Skip any empty lines. if (trim($installed_mods[$i]) == '') continue; // Ignore errors with borked installed.list's. list ($name, $file, $id, $version) = array_pad(explode('|^|', $installed_mods[$i]), 4, ''); // Pretty simple, eh? $installed[] = array( 'name' => stripslashes($name), 'filename' => stripslashes($file), 'id' => $id, 'version' => trim($version) ); } return $installed; } function saveInstalledPackages($instmods) { global $boarddir; // Attempt to make the installed.list file writable if it isn't yet. if (!is_writable($boarddir . '/Packages/installed.list')) @chmod($boarddir . '/Packages/installed.list', 0777); $data = ''; foreach ($instmods as $packageInfo) { if (empty($packageInfo)) continue; $data .= trim($packageInfo['name']) . '|^|' . trim($packageInfo['filename']) . '|^|' . trim($packageInfo['id']) . '|^|' . trim($packageInfo['version']) . "\n"; } file_put_contents($boarddir . '/Packages/installed.list', $data); } function getPackageInfo($gzfilename) { global $boarddir; // Extract package-info.xml from downloaded file. (*/ is used because it could be in any directory.) if (strpos($gzfilename, 'http://') !== false) $packageInfo = read_tgz_file($gzfilename, '*/package-info.xml', true); else { if (!file_exists($boarddir . '/Packages/' . $gzfilename)) return false; $packageInfo = read_tgz_file($boarddir . '/Packages/' . $gzfilename, '*/package-info.xml', true); } // Parse package-info.xml into an xmlArray. $packageInfo = new xmlArray($packageInfo); if (!$packageInfo->exists('package-info[0]')) return false; $packageInfo = $packageInfo->path('package-info[0]'); $package = $packageInfo->to_array(); $package['xml'] = $packageInfo; $package['filename'] = $gzfilename; if (!isset($package['type'])) $package['type'] = 'modification'; return $package; } // Parses a package-info.xml file - method can be 'install', 'upgrade', or 'uninstall'. function parsePackageInfo(&$packageXML, $testing_only = true, $method = 'install', $previous_version = '') { global $boarddir, $forum_version; // Mayday! That action doesn't exist!! if (!$packageXML->exists($method)) return array(); // We haven't found the package script yet... $script = false; $the_version = strtr($forum_version, array('SMF ' => '')); // Get all the versions of this method and find the right one. $these_methods = $packageXML->set($method); foreach ($these_methods as $this_method) { // They specified certain versions this part is for. if ($this_method->exists('@for')) { // Don't keep going if this won't work for this version of SMF. if (!matchPackageVersion($the_version, $this_method->fetch('@for'))) continue; } // Upgrades may go from a certain old version of the mod. if ($method == 'upgrade' && $this_method->exists('@from')) { // Well, this is for the wrong old version... if (!matchPackageVersion($previous_version, $this_method->fetch('@from'))) continue; } // We've found it! $script = $this_method; break; } // Bad news, a matching script wasn't found! if ($script === false) return array(); // Find all the actions in this method - in theory, these should only be allowed actions. (* means all.) $actions = $script->set('*'); $return = array(); $temp_auto = 0; // This is the testing phase... nothing shall be done yet. foreach ($actions as $action) { $actionType = $action->name(); if ($actionType == 'readme' || $actionType == 'code' || $actionType == 'modification') { if ($action->exists('@type') && $action->fetch('@type') == 'inline') { $filename = $boarddir . '/Packages/temp/$auto_' . $temp_auto++ . ($actionType == 'readme' ? '.txt' : ($actionType == 'code' ? '.php' : '.mod')); file_put_contents($filename, $action->fetch('.')); $filename = strtr($filename, array($boarddir . '/Packages/temp/' => '')); } else $filename = $action->fetch('.'); $return[] = array( 'type' => $actionType, 'filename' => $filename, 'description' => '', 'reverse' => $action->exists('@reverse') && $action->fetch('@reverse') == 'true', 'boardmod' => $action->exists('@format') && $action->fetch('@format') == 'boardmod', ); continue; } $this_action = &$return[]; $this_action = array( 'type' => $actionType, 'filename' => $action->fetch('@name'), 'description' => $action->fetch('.') ); // If there is a destination, make sure it makes sense. if (substr($actionType, 0, 6) != 'remove') $this_action['destination'] = parse_path($action->fetch('@destination')) . '/' . basename($this_action['filename']); else $this_action['filename'] = parse_path($this_action['filename']); // If we're moving or requiring (copying) a file if (substr($actionType, 0, 4) == 'move' || substr($actionType, 0, 7) == 'require') { if ($action->exists('@from')) $this_action['source'] = parse_path($action->fetch('@from')); else $this_action['source'] = $boarddir . '/Packages/temp/' . $this_action['filename']; } // Check if these things can be done. (chmod's etc.) if ($actionType == 'create-dir') { if (!mktree($this_action['destination'], false)) { $temp = $this_action['destination']; while (!file_exists($temp) && strlen($temp) > 1) $temp = dirname($temp); $return[] = array( 'type' => 'chmod', 'filename' => $temp ); } } elseif ($actionType == 'create-file') { if (!mktree(dirname($this_action['destination']), false)) { $temp = dirname($this_action['destination']); while (!file_exists($temp) && strlen($temp) > 1) $temp = dirname($temp); $return[] = array( 'type' => 'chmod', 'filename' => $temp ); } if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination'])))) $return[] = array( 'type' => 'chmod', 'filename' => $this_action['destination'] ); } elseif ($actionType == 'require-dir') { if (!mktree($this_action['destination'], false)) { $temp = $this_action['destination']; while (!file_exists($temp) && strlen($temp) > 1) $temp = dirname($temp); $return[] = array( 'type' => 'chmod', 'filename' => $temp ); } } elseif ($actionType == 'require-file') { if (!mktree(dirname($this_action['destination']), false)) { $temp = dirname($this_action['destination']); while (!file_exists($temp) && strlen($temp) > 1) $temp = dirname($temp); $return[] = array( 'type' => 'chmod', 'filename' => $temp ); } if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination'])))) $return[] = array( 'type' => 'chmod', 'filename' => $this_action['destination'] ); } elseif ($actionType == 'move-dir' || $actionType == 'move-file') { if (!mktree(dirname($this_action['destination']), false)) { $temp = dirname($this_action['destination']); while (!file_exists($temp) && strlen($temp) > 1) $temp = dirname($temp); $return[] = array( 'type' => 'chmod', 'filename' => $temp ); } if (!is_writable($this_action['destination']) && (file_exists($this_action['destination']) || !is_writable(dirname($this_action['destination'])))) $return[] = array( 'type' => 'chmod', 'filename' => $this_action['destination'] ); } elseif ($actionType == 'remove-dir') { if (!is_writable($this_action['filename']) && file_exists($this_action['destination'])) $return[] = array( 'type' => 'chmod', 'filename' => $this_action['filename'] ); } elseif ($actionType == 'remove-file') { if (!is_writable($this_action['filename']) && file_exists($this_action['filename'])) $return[] = array( 'type' => 'chmod', 'filename' => $this_action['filename'] ); } } // Only testing - just return a list of things to be done. if ($testing_only) return $return; umask(0); $failure = false; $not_done = array(array('type' => '!')); foreach ($return as $action) { if ($action['type'] == 'modification' || $action['type'] == 'code') $not_done[] = $action; if ($action['type'] == 'create-dir') $failure |= !mktree($action['destination'], 0777); elseif ($action['type'] == 'create-file') { $failure |= !mktree(dirname($action['destination']), 0777); // Create an empty file. $f = fopen($action['destination'], 'w'); if ($f == false) $failure = true; else fclose($f); } elseif ($action['type'] == 'require-dir') copytree($action['source'], $action['destination']); elseif ($action['type'] == 'require-file') { $failure |= !mktree(dirname($action['destination']), 0777); if (!is_writable($action['destination'])) @chmod($action['destination'], 0777); $failure |= !copy($action['source'], $action['destination']); } elseif ($action['type'] == 'move-dir' || $action['type'] == 'move-file') { $failure |= !mktree(dirname($action['destination']), 0777); $failure |= !rename($action['source'], $action['destination']); } elseif ($action['type'] == 'remove-dir') deltree($action['filename']); elseif ($action['type'] == 'remove-file') { if (!is_writable($action['filename'])) @chmod($action['filename'], 0777); $failure |= !unlink($action['filename']); } } return $not_done; } // This is such a pain I created a function for it :P. function matchPackageVersion($version, $versions) { $for = explode(',', $versions); // Trim them all! for ($i = 0, $n = count($for); $i < $n; $i++) $for[$i] = trim($for[$i]); // The version is explicitly defined... too easy. if (in_array($version, $for)) return true; foreach ($for as $list) { // Look for a version specification like "1.0-1.2". if (strpos($list, '-') === false) continue; list ($lower, $upper) = explode('-', $list); if (trim($lower) < $version && trim($upper) > $version) return true; } // Well, I guess it doesn't match... return false; } function parse_path($path) { global $modSettings, $boarddir, $sourcedir, $settings; $search = array( '\\', '$boarddir', '$sourcedir', '$avatardir', '$themedir', '$languagedir', '$smileysdir', ); $replace = array( '/', $boarddir, $sourcedir, $modSettings['avatar_directory'], $settings['default_theme_dir'], $settings['default_theme_dir'] . '/languages', $modSettings['smileys_dir'], ); return str_replace($search, $replace, $path); } function deltree($dir, $delete_dir = true) { if (!file_exists($dir)) return; $current_dir = opendir($dir); if ($current_dir == false) return; while ($entryname = readdir($current_dir)) { if (in_array($entryname, array('.', '..'))) continue; if (is_dir($dir . '/' . $entryname)) deltree($dir . '/' . $entryname); else { if (!is_writable($dir . '/' . $entryname)) @chmod($dir . '/' . $entryname, 0777); unlink($dir . '/' . $entryname); } } closedir($current_dir); if ($delete_dir) { if (!is_writable($dir)) @chmod($dir, 0777); rmdir($dir); } } function mktree($strPath, $mode) { if (is_dir($strPath)) { if (!is_writable($strPath) && $mode !== false) @chmod($strPath, $mode); return is_writable($strPath); } if (!mktree(dirname($strPath), $mode)) return false; if (!is_writable(dirname($strPath)) && $mode !== false) @chmod(dirname($strPath), $mode); return $mode === false ? is_writable(dirname($strPath)) || file_exists($strPath) : mkdir($strPath, $mode); } function copytree($source, $destination) { if (!file_exists($destination)) mktree($destination, 0777); elseif (!is_writable($destination)) @chmod($destination, 0777); $current_dir = opendir($source); if ($current_dir == false) return; while ($entryname = readdir($current_dir)) { if (in_array($entryname, array('.', '..'))) continue; if (is_dir($source . '/' . $entryname)) copytree($source . '/' . $entryname, $destination . '/' . $entryname); else copy($source . '/' . $entryname, $destination . '/' . $entryname); } closedir($current_dir); } // Parse an xml based modification file. function parseModification($file, $testing = true, $undo = false) { global $boarddir, $sourcedir, $settings, $txt, $modSettings; @set_time_limit(600); $xml = new xmlArray(strtr($file, array("\r" => ''))); $actions = array(); $everything_found = true; if (!$xml->exists('modification') || !$xml->exists('modification/file')) { $actions[] = array( 'type' => 'error', 'filename' => '-', 'debug' => $txt['package_modification_malformed'] ); return $actions; } $files = $xml->set('modification/file'); foreach ($files as $file) { $working_file = parse_path(trim($file->fetch('@name'))); // Doesn't exist - give an error or what? if (!file_exists($working_file) && (!$file->exists('@error') || (trim($file->fetch('@error')) != 'ignore' && trim($file->fetch('@error')) != 'skip'))) { $actions[] = array( 'type' => 'missing', 'filename' => $working_file, 'debug' => $txt['package_modification_missing'] ); $everything_found = false; continue; } // Skip the file if it doesn't exist. elseif (!file_exists($working_file) && $file->exists('@error') && trim($file->fetch('@error')) == 'skip') continue; // Okay, we're creating this file then...? elseif (!file_exists($working_file)) $working_data = ''; // Phew, it exists! Load 'er up! else $working_data = str_replace("\r", '', implode('', file($working_file))); $actions[] = array( 'type' => 'opened', 'filename' => $working_file ); $operations = $file->exists('operation') ? $file->set('operation') : array(); foreach ($operations as $operation) { // Special case: find the end of the file! if ($operation->exists('search/@position') && trim($operation->fetch('search/@position')) == 'end') { $replace_with = $operation->fetch('add'); if (substr(rtrim($working_data), -3) == "\n?" . '>') $working_data = substr(rtrim($working_data), 0, -3) . $replace_with . "\n?" . '>'; else $working_data = $working_data . $replace_with; $actions[] = array( 'type' => 'append', 'filename' => $working_file, 'add' => $replace_with ); } // Otherwise we will need to look for the search data. else { // Start with what we have.... $working_search = ''; $replace_with = $operation->exists('add') ? $operation->fetch('add') : ''; // Are we doing a fancy-shmancy regexp search? $regexp = false; $temp_searches = $operation->set('search'); if (count($temp_searches) > 1) { // Resort the list so replace comes first - it must. $searches = array(); foreach ($temp_searches as $i => $v) { // A quick check on whether it's a regular expression search... if (!$undo && $temp_searches[$i]->exists('@regexp') && trim($temp_searches[$i]->fetch('@regexp')) == 'true') $regexp = true; if (!$v->exists('@position') || ($v->fetch('@position') != 'before' && $v->fetch('@position') != 'after')) { $searches[] = $v; unset($temp_searches[$i]); } } // Add on the rest, in the order they were in. $searches = array_merge($searches, $temp_searches); } else { $searches = $temp_searches; $regexp = !$undo && $searches[0]->exists('@regexp') && trim($searches[0]->fetch('@regexp')) == 'true'; } // If we're not using regular expression search, replace out any $'s and \'s! if (!$regexp) { // Shuzzup. This is done so we can safely use a regular expression. ($0 is bad!!) if (!$undo) $replace_with = strtr($replace_with, array('$' => '[$PACK' . 'AGE1$]', '\\' => '[$PACK' . 'AGE2$]')); else $replace_with = preg_quote($replace_with, '~'); } // Go through all the search conditions. foreach ($searches as $search) { $this_clean_search = $search->fetch('.'); // Are we not using regular expressions explicitly? if (!$regexp) { $this_search = preg_quote($this_clean_search, '~'); // Remember, can't have whitespace correction and regexp on at the same time. if ($search->exists('@whitespace') && trim($search->fetch('@whitespace')) != 'loose') $this_search = preg_replace('~[ \t]+~', '[ \t]+', $this_search); // Shuzzup again. Read the comment above a few lines where this is done to $replace_with... if ($undo) $working_search = strtr($working_search, array('$' => '[$PACK' . 'AGE1$]', '\\' => '[$PACK' . 'AGE2$]')); } else $this_search = $this_clean_search; // Get the position to replace on. $position = $search->exists('@position') ? trim($search->fetch('@position')) : 'replace'; if ($position == 'before') { if (!$regexp) { if (!$undo) { $working_search = '(' . $this_search . ')' . $working_search; $replace_with = '$1' . $replace_with; } else { $working_search = '$1' . $working_search; $replace_with = '(' . $this_search . ')' . $replace_with; } } else { $working_search = $this_search . $working_search; $replace_with = $this_clean_search . $replace_with; } } elseif ($position == 'after') { if (!$regexp) { if (!$undo) { $working_search = $working_search . '(' . $this_search . ')'; $replace_with = $replace_with . '$1'; } else { $working_search = $working_search . '$1'; $replace_with = $replace_with . '(' . $this_search . ')'; } } else { $working_search = $working_search . $this_search; $replace_with = $replace_with . $this_clean_search; } } else { if (!$undo) $working_search = $this_search; else $working_search = $this_clean_search; } } if ($undo) { $temp = $replace_with; $replace_with = $working_search; $working_search = $temp; // We can't undo this! if (trim($working_search) == '' || trim($replace_with) == '') continue; } elseif ($working_search == '') continue; $failed = preg_match('~' . $working_search . '~s', $working_data) == 0; if ($failed && (!$operation->exists('@error') || $operation->fetch('@error') == 'fatal')) { $actions[] = array( 'type' => 'failure', 'filename' => $working_file, 'search' => $working_search ); $everything_found = false; continue; } elseif (!$failed && $operation->exists('@error') && $operation->fetch('@error') == 'required') { $actions[] = array( 'type' => 'failure', 'filename' => $working_file, 'search' => $working_search ); $everything_found = false; continue; } if ($replace_with == '') continue; $working_data = preg_replace('~' . $working_search . '~s', $replace_with, $working_data); $actions[] = array( 'type' => 'replace', 'filename' => $working_file, 'search' => $working_search, 'replace' => $replace_with ); } } // Fix any little helper symbols ;). $working_data = strtr($working_data, array('[$PACK' . 'AGE1$]' => '$', '[$PACK' . 'AGE2$]' => '\\')); if (file_exists($working_file) && !is_writable($working_file)) @chmod($working_file, 0755); if (file_exists($working_file) && !is_writable($working_file)) @chmod($working_file, 0777); if (!file_exists($working_file) && !is_writable(dirname($working_file))) @chmod(dirname($working_file), 0755); if (!file_exists($working_file) && !is_writable(dirname($working_file))) @chmod(dirname($working_file), 0777); if ((file_exists($working_file) && !is_writable($working_file)) || !is_writable(dirname($working_file))) $actions[] = array( 'type' => 'chmod', 'filename' => $working_file ); if (basename($working_file) == 'Settings_bak.php') continue; if (!$testing) { if (!empty($modSettings['package_make_backups']) && file_exists($working_file)) { // No, no, not Settings.php! if (basename($working_file) == 'Settings.php') copy($working_file, dirname($working_file) . '/Settings_bak.php'); else copy($working_file, $working_file . '~'); } file_put_contents($working_file, $working_data); } $actions[] = array( 'type' => 'saved', 'filename' => $working_file ); } $actions[] = array( 'type' => 'result', 'status' => $everything_found ); return $actions; } // Parses a BoardMod format mod file... function parseBoardMod($file, $testing = true, $undo = false) { global $boarddir, $sourcedir, $settings, $txt, $modSettings; @set_time_limit(600); $file = strtr($file, array("\r" => '')); $working_file = null; $working_search = null; $working_data = ''; $replace_with = null; $actions = array(); $everything_found = true; while (preg_match('~<(edit file|file|search|search for|add|add after|replace|add before|before)>\n(.*?)\n~is', $file, $code_match) != 0) { // Edit a specific file. if ($code_match[1] == 'file' || $code_match[1] == 'edit file') { // Backup the old file. if ($working_file !== null) { if (file_exists($working_file) && !is_writable($working_file)) @chmod($working_file, 0777); elseif (!is_writable(dirname($working_file))) @chmod(dirname($working_file), 0777); // Don't even dare. if (basename($working_file) == 'Settings_bak.php') continue; if (!is_writable($working_file)) $actions[] = array( 'type' => 'chmod', 'filename' => $working_file ); if (!$testing) { if (!empty($modSettings['package_make_backups']) && file_exists($working_file)) { if (basename($working_file) == 'Settings.php') copy($working_file, dirname($working_file) . '/Settings_bak.php'); else copy($working_file, $working_file . '~'); } file_put_contents($working_file, $working_data); } } if ($working_file !== null) $actions[] = array( 'type' => 'saved', 'filename' => $working_file ); // Make sure the file exists! $code_match[2] = parse_path($code_match[2]); $working_file = $code_match[2]; if (!file_exists($code_match[2])) { $places_to_check = array($boarddir, $sourcedir, $settings['default_theme_dir'], $settings['default_theme_dir'] . '/languages'); foreach ($places_to_check as $place) if (file_exists($place . '/' . $code_match[2])) { $code_match[2] = $place . '/' . $code_match[2]; break; } } if (file_exists($code_match[2])) { // Load the new file. $working_data = str_replace("\r", '', implode('', file($code_match[2]))); $actions[] = array( 'type' => 'opened', 'filename' => $working_file ); } else { $actions[] = array( 'type' => 'missing', 'filename' => $working_file ); $working_file = null; $everything_found = false; } // Can't be searching for something... $working_search = null; } // Search for a specific string. elseif (($code_match[1] == 'search' || $code_match[1] == 'search for') && $working_file !== null) { if ($working_search !== null) { $actions[] = array( 'type' => 'error', 'filename' => $working_file ); $everything_found = false; } $working_search = $code_match[2]; } // Must've already loaded a search string. elseif ($working_search !== null) { // This is the base string.... $replace_with = $code_match[2]; // Add this afterward... if ($code_match[1] == 'add' || $code_match[1] == 'add after') $replace_with = $working_search . "\n" . $replace_with; // Add this beforehand. elseif ($code_match[1] == 'before' || $code_match[1] == 'add before') $replace_with .= "\n" . $working_search; // Otherwise.. replace with $replace_with ;). } // If we have a search string, replace string, and open file.. if ($working_search !== null && $replace_with !== null && $working_file !== null) { // Make sure it's somewhere in the string. if ($undo) { $temp = $replace_with; $replace_with = $working_search; $working_search = $temp; } if (strpos($working_data, $working_search) !== false) { $working_data = str_replace($working_search, $replace_with, $working_data); $actions[] = array( 'type' => 'replace', 'filename' => $working_file, 'search' => $working_search, 'replace' => $replace_with ); } // It wasn't found! else { $actions[] = array( 'type' => 'failure', 'filename' => $working_file, 'search' => $working_search ); $everything_found = false; } // These don't hold any meaning now. $working_search = null; $replace_with = null; } // Get rid of the old tag. $file = substr_replace($file, '', strpos($file, $code_match[0]), strlen($code_match[0])); } // Backup the old file. if ($working_file !== null) { if (file_exists($working_file) && !is_writable($working_file)) @chmod($working_file, 0777); elseif (!is_writable(dirname($working_file))) @chmod(dirname($working_file), 0777); if (!is_writable($working_file)) $actions[] = array( 'type' => 'chmod', 'filename' => $working_file ); if (!$testing) { if (!empty($modSettings['package_make_backups']) && file_exists($working_file)) copy($working_file, $working_file . '~'); file_put_contents($working_file, $working_data); } } if ($working_file !== null) $actions[] = array( 'type' => 'saved', 'filename' => $working_file ); $actions[] = array( 'type' => 'result', 'status' => $everything_found ); return $actions; } // Create a file - this is defined in PHP 5, just use the same function name. if (!function_exists('file_put_contents')) { function file_put_contents($filename, $data) { $text_filetypes = array('php', 'txt', '.js', 'css', 'vbs', 'tml', 'htm'); $fp = fopen($filename, in_array(substr($filename, -3), $text_filetypes) ? 'w' : 'wb'); if (!$fp) return 0; fwrite($fp, $data); fclose($fp); return strlen($data); } } // An xml array. Reads in xml, allows you to access it simply. Version 1.1. class xmlArray { // The array and debugging output level. var $array, $debug_level, $trim; // Create an xml array. // the xml data, trim elements?, debugging output level, reserved. //ie. $xml = new xmlArray(file('data.xml')); function xmlArray($data, $auto_trim = false, $level = null, $is_clone = false) { // Set the debug level. $this->debug_level = $level !== null ? $level : error_reporting(); $this->trim = $auto_trim; // Is the data already parsed? if ($is_clone) { $this->array = $data; return; } // Is the input an array? (ie. passed from file()?) if (is_array($data)) $data = implode('', $data); // Remove any xml declaration or doctype, and parse out comments and CDATA. $data = $this->_to_cdata(preg_replace(array('/^<\?xml.+?\?>/is', '/]+?>/s', '//s'), '', $data)); // Now parse the xml! $this->array = $this->_parse($data); } // Get the root element's name. //ie. echo $element->name(); function name() { return isset($this->array['name']) ? $this->array['name'] : ''; } // Get a specified element's value or attribute by path. // the path to the element to fetch, whether to include elements? //ie. $data = $xml->fetch('html/head/title'); function fetch($path, $get_elements = false) { // Get the element, in array form. $array = $this->path($path); if ($array === false) return false; // Getting elements into this is a bit complicated... if ($get_elements && !is_string($array)) { $temp = ''; // Use the _xml() function to get the xml data. foreach ($array->array as $val) { // Skip the name and any attributes. if (is_array($val)) $temp .= $this->_xml($val, null); } // Just get the XML data and then take out the CDATAs. return $this->_to_cdata($temp); } // Return the value - taking care to pick out all the text values. return is_string($array) ? $array : $this->_fetch($array->array); } // Get an element, returns a new xmlArray. // the path to the element to get, always return full result set? (ie. don't contract a single item.) //ie. $element = $xml->path('html/body'); function path($path, $return_full = false) { // Split up the path. $path = explode('/', $path); // Start with a base array. $array = $this->array; // For each element in the path. foreach ($path as $el) { // Deal with sets.... if (strpos($el, '[') !== false) { $lvl = (int) substr($el, strpos($el, '[') + 1); $el = substr($el, 0, strpos($el, '[')); } // Find an attribute. elseif (substr($el, 0, 1) == '@') { // It simplifies things if the attribute is already there ;). if (isset($array[$el])) return $array[$el]; else { // Cause an error. if ($this->debug_level & E_NOTICE) trigger_error('Undefined XML attribute: ' . substr($el, 1), E_USER_NOTICE); return false; } } else $lvl = null; // Find this element. $array = $this->_path($array, $el, $lvl); } // Clean up after $lvl, for $return_full. if ($return_full && (!isset($array['name']) || substr($array['name'], -1) != ']')) $array = array('name' => $el . '[]', $array); // Create the right type of class... $newClass = get_class($this); // Return a new xmlArray for the result. return $array === false ? false : new $newClass($array, $this->trim, $this->debug_level, true); } // Check if an element exists. // the path to the element to get. //ie. echo $xml->exists('html/body') ? 'y' : 'n'; function exists($path) { // Split up the path. $path = explode('/', $path); // Start with a base array. $array = $this->array; // For each element in the path. foreach ($path as $el) { // Deal with sets.... if (strpos($el, '[') !== false) { $lvl = (int) substr($el, strpos($el, '[') + 1); $el = substr($el, 0, strpos($el, '[')); } // Find an attribute. elseif (substr($el, 0, 1) == '@') return isset($array[$el]); else $lvl = null; // Find this element. $array = $this->_path($array, $el, $lvl, true); } return $array !== false; } // Count the number of occurances of a path. // the path to search for. //ie. echo $xml->count('html/head/meta'); function count($path) { // Get the element, always returning a full set. $temp = $this->path($path, true); // Start at zero, then count up all the numeric keys. $i = 0; foreach ($temp->array as $item) { if (is_array($item)) $i++; } return $i; } // Get an array of xmlArray's for use with foreach. // the path to search for. //ie. foreach ($xml->set('html/body/p') as $p) function set($path) { // None as yet, just get the path. $array = array(); $xml = $this->path($path, true); foreach ($xml->array as $val) { // Skip these, they aren't elements. if (!is_array($val) || $val['name'] == '!') continue; // Create the right type of class... $newClass = get_class($this); // Create a new xmlArray and stick it in the array. $array[] = new $newClass($val, $this->trim, $this->debug_level, true); } return $array; } // Create an xml file from an xml array. // the path to the element. (optional) //ie. echo $this->create_xml() function create_xml($path = null) { // Was a path specified? If so, use that array. if ($path !== null) { $path = $this->path($path); // The path was not found!!! if ($path === false) return false; $path = $path->array; } // Just use the current array. else $path = $this->array; // Add the xml declaration to the front. return '' . $this->_xml($path, 0); } // Output the xml in an array form. // the path to output. //ie. print_r($xml->to_array()); function to_array($path = null) { // Are we doing a specific path? if ($path !== null) { $path = $this->path($path); // The path was not found!!! if ($path === false) return false; $path = $path->array; } // No, so just use the current array. else $path = $this->array; return $this->_array($path); } // Parse data into an array. (privately used...) function _parse($data) { // Start with an 'empty' array with no data. $current = array( ); // Loop until we're out of data. while ($data != '') { // Find and remove the next tag. preg_match('/\A<([\w\-:]+)((?:\s+.+?)?)(\s\/)?>/', $data, $match); if (isset($match[0])) $data = preg_replace('/' . preg_quote($match[0], '/') . '/s', '', $data, 1); // Didn't find a tag? Keep looping.... if (!isset($match[1]) || $match[1] == '') { // If there's no <, the rest is data. if (strpos($data, '<') === false) { $text_value = $this->_from_cdata($data); $data = ''; if ($text_value != '') $current[] = array( 'name' => '!', 'value' => $text_value ); } // If the < isn't immediately next to the current position... more data. elseif (strpos($data, '<') > 0) { $text_value = $this->_from_cdata(substr($data, 0, strpos($data, '<'))); $data = substr($data, strpos($data, '<')); if ($text_value != '') $current[] = array( 'name' => '!', 'value' => $text_value ); } // If we're looking at a with no start, kill it. elseif (strpos($data, '<') !== false && strpos($data, '<') == 0) { if (strpos($data, '<', 1) !== false) { $text_value = $this->_from_cdata(substr($data, 0, strpos($data, '<', 1))); $data = substr($data, strpos($data, '<', 1)); if ($text_value != '') $current[] = array( 'name' => '!', 'value' => $text_value ); } else { $text_value = $this->_from_cdata($data); $data = ''; if ($text_value != '') $current[] = array( 'name' => '!', 'value' => $text_value ); } } // Wait for an actual occurance of an element. continue; } // Create a new element in the array. $el = &$current[]; $el['name'] = $match[1]; // If this ISN'T empty, remove the close tag and parse the inner data. if ((!isset($match[3]) || $match[3] != ' /') && (!isset($match[2]) || $match[2] != ' /')) { $tag = preg_quote($match[1], '/'); $reg = '/\A(.*?(<' . $tag . ' .*?>.*?<\/' . $tag . '>.*?)*?)<\/' . $tag . '>/s'; // Remove the element and fetch the inner data. preg_match($reg, $data, $inner_match); $data = preg_replace($reg, '', $data, 1); if (!isset($inner_match[1])) continue; // Parse the inner data. if (strpos($inner_match[1], '<') !== false) $el += $this->_parse($inner_match[1]); elseif (trim($inner_match[1]) != '') { $text_value = $this->_from_cdata($inner_match[1]); if ($text_value != '') $el[] = array( 'name' => '!', 'value' => $text_value ); } } // If we're dealing with attributes as well, parse them out. if (isset($match[2]) && $match[2] != '') { // Find all the attribute pairs in the string. preg_match_all('/([\w:]+)="(.+?)"/', $match[2], $attr, PREG_SET_ORDER); // Set them as @attribute-name. foreach ($attr as $match_attr) $el['@' . $match_attr[1]] = $match_attr[2]; } } // Return the parsed array. return $current; } // Get a specific element's xml. (priavtely used...) function _xml($array, $indent) { $indentation = $indent !== null ? ' ' . str_repeat(' ', $indent) : ''; // This is a set of elements, with no name... if (is_array($array) && !isset($array['name'])) { $temp = ''; foreach ($array as $val) $temp .= $this->_xml($val, $indent); return $temp; } // This is just text! if ($array['name'] == '!') return $indentation . ''; elseif (substr($array['name'], -2) == '[]') $array['name'] = substr($array['name'], 0, -2); // Start the element. $output = $indentation . '<' . $array['name']; $inside_elements = false; $output_el = ''; // Run through and recurively output all the elements or attrbutes inside this. foreach ($array as $k => $v) { if (substr($k, 0, 1) == '@') $output .= ' ' . substr($k, 1) . '="' . $v . '"'; elseif (is_array($v)) { $output_el .= $this->_xml($v, $indent === null ? null : $indent + 1); $inside_elements = true; } } // Indent, if necessary.... then close the tag. if ($inside_elements) $output .= '>' . $output_el . $indentation . ''; else $output .= ' />'; return $output; } // Return an element as an array... function _array($array) { $return = array(); $text = ''; foreach ($array as $value) { if (!is_array($value) || !isset($value['name'])) continue; if ($value['name'] == '!') $text .= $value['value']; else $return[$value['name']] = $this->_array($value); } if (empty($return)) return $text; else return $return; } // Parse out CDATA tags. (htmlspecialchars them...) function _to_cdata($data) { // Match all of the CDATA tags. preg_match_all('//s', $data, $match, PREG_SET_ORDER); // Replace them with htmlentities'd versions. foreach ($match as $m) $data = str_replace($m[0], htmlentities($m[1], ENT_QUOTES), $data); return $data; } // Turn the CDATAs back to normal text. function _from_cdata($data) { // Get the HTML translation table and reverse it. $trans_tbl = array_flip(get_html_translation_table(HTML_ENTITIES, ENT_QUOTES)); // Translate all the entities out. $data = strtr(preg_replace('~&#(\d{1,4});~e', "chr('\$1')", $data), $trans_tbl); return $this->trim ? trim($data) : $data; } // Given an array, return the text from that array. (recursive and privately used.) function _fetch($array) { // Don't return anything if this is just a string. if (is_string($array)) return ''; $temp = ''; foreach ($array as $text) { // This means it's most likely an attribute or the name itself. if (!isset($text['name'])) continue; // This is text! if ($text['name'] == '!') $temp .= $text['value']; // Another element - dive in ;). else $temp .= $this->_fetch($text); } // Return all the bits and pieces we've put together. return $temp; } // Get a specific array by path, one level down. (privately used...) function _path($array, $path, $level, $no_error = false) { // Is $array even an array? It might be false! if (!is_array($array)) return false; // Asking for *no* path? if ($path == '' || $path == '.') return $array; $paths = explode('|', $path); // A * means all elements of any name. $show_all = in_array('*', $paths); $results = array(); // Check each element. foreach ($array as $value) { if (!is_array($value) || $value['name'] === '!') continue; if ($show_all || in_array($value['name'], $paths)) { // Skip elements before "the one". if ($level !== null && $level > 0) $level--; else $results[] = $value; } } // No results found... if (empty($results)) { // Cause an error. if ($this->debug_level & E_NOTICE && !$no_error) trigger_error('Undefined XML element: ' . $path, E_USER_NOTICE); return false; } // Only one result. elseif (count($results) == 1 || $level !== null) return $results[0]; // Return the result set. else return $results + array('name' => $path . '[]'); } } // http://www.faqs.org/rfcs/rfc959.html class ftp_connection { var $connection = 'no_connection', $error = false; // Create a new FTP connection... function ftp_connection($ftp_server, $ftp_port, $ftp_user, $ftp_pass) { // Connect to the FTP server. $this->connection = @fsockopen($ftp_server, $ftp_port, $err, $err, 5); if (!$this->connection) { $this->error = 'bad_server'; return; } // Get the welcome message... if (!$this->check_response(220)) { echo $this->error = 'bad_response'; return; } // Send the username, it should ask for a password. fwrite($this->connection, 'USER ' . $ftp_user . "\r\n"); if (!$this->check_response(331)) { $this->error = 'bad_username'; return; } // Now send the password... and hope it goes okay. fwrite($this->connection, 'PASS ' . $ftp_pass . "\r\n"); if (!$this->check_response(230)) { $this->error = 'bad_password'; return; } } function chdir($ftp_path) { if (!is_resource($this->connection)) return false; // No slash on the end, please... if (substr($ftp_path, -1) == '/') $ftp_path = substr($ftp_path, 0, -1); fwrite($this->connection, 'CWD ' . $ftp_path . "\r\n"); if (!$this->check_response(250)) { $this->error = 'bad_path'; return false; } return true; } function chmod($ftp_file, $chmod) { if (!is_resource($this->connection)) return false; // Convert the chmod value from octal (0777) to text ("777"). fwrite($this->connection, 'SITE CHMOD ' . decoct($chmod) . ' ' . $ftp_file . "\r\n"); if (!$this->check_response(200)) { $this->error = 'bad_file'; return false; } return true; } function unlink($ftp_file) { // We are actually connected, right? if (!is_resource($this->connection)) return false; // Delete file X. fwrite($this->connection, 'DELE ' . $ftp_file . "\r\n"); if (!$this->check_response(250)) { $this->error = 'bad_file'; return false; } return true; } function check_response($desired) { // Wait for a response that isn't continued with -, but don't wait too long. $time = time(); do $response = fgets($this->connection, 1024); while (substr($response, 3, 1) != ' ' && time() - $time < 5); // Was the desired response returned? return substr($response, 0, 3) == $desired; } function close() { // Goodbye! fwrite($this->connection, "QUIT\r\n"); fclose($this->connection); return true; } } ?>