2142 lines
60 KiB
PHP
2142 lines
60 KiB
PHP
<?php
|
|
/******************************************************************************
|
|
* Subs-Package.php *
|
|
*******************************************************************************
|
|
* SMF: Simple Machines Forum *
|
|
* Open-Source Project Inspired by Zef Hemel (zef@zefhemel.com) *
|
|
* =========================================================================== *
|
|
* Software Version: SMF 1.0.3 *
|
|
* Software by: Simple Machines (http://www.simplemachines.org) *
|
|
* Copyright 2001-2005 by: Lewis Media (http://www.lewismedia.com) *
|
|
* Support, News, Updates at: http://www.simplemachines.org *
|
|
*******************************************************************************
|
|
* This program is free software; you may redistribute it and/or modify it *
|
|
* under the terms of the provided license as published by Lewis Media. *
|
|
* *
|
|
* This program is distributed in the hope that it is and will be useful, *
|
|
* but WITHOUT ANY WARRANTIES; without even any implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* *
|
|
* See the "license.txt" file for details of the Simple Machines license. *
|
|
* The latest version can always be found at http://www.simplemachines.org. *
|
|
******************************************************************************/
|
|
if (!defined('SMF'))
|
|
die('Hacking attempt...');
|
|
|
|
/* This file's central purpose of existence is that of making the package
|
|
manager work nicely. It contains functions for handling tar.gz and zip
|
|
files, as well as a simple xml parser to handle the xml package stuff.
|
|
Not to mention a few functions to make file handling easier.
|
|
|
|
array read_tgz_file(string filename, string destination,
|
|
bool single_file = false, bool overwrite = false)
|
|
- reads a .tar.gz file, filename, in and extracts file(s) from it.
|
|
- essentially just a shortcut for read_tgz_data().
|
|
|
|
array read_tgz_data(string data, string destination,
|
|
bool single_file = false, bool overwrite = false)
|
|
- extracts a file or files from the .tar.gz contained in data.
|
|
- detects if the file is really a .zip file, and if so returns the
|
|
result of read_zip_data
|
|
- if destination is null, returns a list of files in the archive.
|
|
- if single_file is true, returns the contents of the file specified
|
|
by destination, if it exists, or false.
|
|
- if single_file is true, destination can start with * and / to
|
|
signify that the file may come from any directory.
|
|
- destination should not begin with a / if single_file is true.
|
|
- overwrites existing files with newer modification times if and
|
|
only if overwrite is true.
|
|
- creates the destination directory if it doesn't exist, and is
|
|
is specified.
|
|
- requires zlib support be built into PHP.
|
|
- returns an array of the files extracted.
|
|
|
|
array read_zip_data(string data, string destination,
|
|
bool single_file = false, bool overwrite = false)
|
|
- extracts a file or files from the .zip contained in data.
|
|
- if destination is null, returns a list of files in the archive.
|
|
- if single_file is true, returns the contents of the file specified
|
|
by destination, if it exists, or false.
|
|
- if single_file is true, destination can start with * and / to
|
|
signify that the file may come from any directory.
|
|
- destination should not begin with a / if single_file is true.
|
|
- overwrites existing files with newer modification times if and
|
|
only if overwrite is true.
|
|
- creates the destination directory if it doesn't exist, and is
|
|
is specified.
|
|
- requires zlib support be built into PHP.
|
|
- returns an array of the files extracted.
|
|
|
|
bool url_exists(string url)
|
|
- checks to see if url is valid, and returns a 200 status code.
|
|
- will return false if the file is "moved permanently" or similar.
|
|
- returns true if the remote url exists.
|
|
|
|
array loadInstalledPackages()
|
|
- loads and returns an array of installed packages.
|
|
- gets this information from Packages/installed.list.
|
|
- returns the array of data.
|
|
|
|
void saveInstalledPackages(array installed_mods)
|
|
- saves an array (as generated by loadInstalledPackages) as the
|
|
list of installed packages.
|
|
- writes this information to Packages/installed.list.
|
|
- requires the above file to be writable.
|
|
|
|
array getPackageInfo(string filename)
|
|
- loads a package's information and returns a representative array.
|
|
- expects the file to be a package in Packages/.
|
|
- returns false if the package-info is invalid.
|
|
- returns a basic array of id, version, filename, and similar
|
|
information.
|
|
- in the array returned, an xmlArray is available in 'xml'.
|
|
|
|
array parsePackageInfo(xmlArray &package, bool testing_only = true,
|
|
string method = 'install', string previous_version = '')
|
|
- parses the actions in package-info.xml files from packages.
|
|
- package should be an xmlArray with package-info as its base.
|
|
- testing_only should be true if the package should not actually be
|
|
applied.
|
|
- method is upgrade, install, or uninstall. Its default is install.
|
|
- previous_version should be set to the previous installed version
|
|
of this package, if any.
|
|
- does not handle failure terribly well; testing first is always
|
|
better.
|
|
- returns an array of those changes made.
|
|
|
|
bool matchPackageVersion(string version, string versions)
|
|
- checks if version matches any of the versions in versions.
|
|
- supports comma separated version numbers, with or without
|
|
whitespace.
|
|
- supports lower and upper bounds. (1.0-1.2)
|
|
- returns true if the version matched.
|
|
|
|
string parse_path(string path)
|
|
- parses special identifiers out of the specified path.
|
|
- returns the parsed path.
|
|
|
|
void deltree(string path, bool delete_directory = false)
|
|
- deletes a directory, and all the files and direcories inside it.
|
|
- requires access to delete these files.
|
|
|
|
bool mktree(string path, int mode)
|
|
- creates the specified tree structure with the mode specified.
|
|
- creates every directory in path until it finds one that already
|
|
exists.
|
|
- returns true if successful, false otherwise.
|
|
|
|
void copytree(string source, string destination)
|
|
- copies one directory structure over to another.
|
|
- requires the destination to be writable.
|
|
|
|
array parseModification(string file, bool testing = true, bool undo = false)
|
|
- parses a xml-style modification file (file).
|
|
- testing tells it the modifications shouldn't actually be saved.
|
|
- undo specifies that the modifications the file requests should be
|
|
undone; this doesn't work with everything (regular expressions.)
|
|
- returns an array of those changes made.
|
|
|
|
array parseBoardMod(string file, bool testing = true, bool undo = false)
|
|
- parses a boardmod-style modification file (file).
|
|
- testing tells it the modifications shouldn't actually be saved.
|
|
- undo specifies that the modifications the file requests should be
|
|
undone.
|
|
- returns an array of those changes made.
|
|
|
|
int file_put_contents(string filename, string data)
|
|
- only defined if it doesn't already exist. (PHP 5)
|
|
- writes data to a file, almost exactly like the built in function.
|
|
- uses text mode for text mode file extensions.
|
|
- returns the number of bytes written.
|
|
|
|
---------------------------------------------------------------------------
|
|
The following functions are all within the xmlArray class, which is the xml
|
|
parser. There are more functions, but these are the ones that should be
|
|
used from outside the class:
|
|
|
|
class xmlArray(string data, bool auto_trim = false,
|
|
int error_level = error_reporting(), bool is_clone = false)
|
|
- creates a new xmlArray, which is an simple xml dom parser.
|
|
- data should be the xml data or an array of, unless is_clone is true.
|
|
- auto_trim can be used to automatically trim textual data.
|
|
- error_level specifies whether notices should be generated for
|
|
missing elements and attributes.
|
|
- if is_clone is true, the xmlArray is cloned from another - used
|
|
internally only.
|
|
|
|
string xmlArray::name()
|
|
- retrieves the name of the current element, usually ''.
|
|
|
|
string xmlArray::fetch(string path, bool get_elements = false)
|
|
- retrieves the textual value of the specified path.
|
|
- children are parsed for text, but only textual data is returned
|
|
unless get_elements is true.
|
|
|
|
xmlArray xmlArray::path(string path, bool return_set = false)
|
|
- finds any elements that match the path specified.
|
|
- will always return a set if there is more than one of the element
|
|
or return_set is true.
|
|
- returns in the form of a new xmlArray.
|
|
|
|
bool xmlArray::exists(string path)
|
|
- returns whether the specified path matches at least one element.
|
|
|
|
int xmlArray::count(string path)
|
|
- returns the number of elements the path matches.
|
|
|
|
array xmlArray::set(string path)
|
|
- returns an array of xmlArray's matching the specified path.
|
|
- this differs from ->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</\\1>~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', '/<!DOCTYPE[^>]+?>/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 '<?xml version="1.0"?' . '>' . $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 </something> 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 . '<![CDATA[' . $array['value'] . ']]>';
|
|
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 . '</' . $array['name'] . '>';
|
|
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('/<!\[CDATA\[(.*?)\]\]>/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;
|
|
}
|
|
}
|
|
|
|
?>
|