1 <?php
2 /**
3 * @package Joomla.Libraries
4 * @subpackage Installer
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 jimport('joomla.filesystem.file');
13 jimport('joomla.filesystem.folder');
14 jimport('joomla.filesystem.path');
15
16 /**
17 * Installer helper class
18 *
19 * @since 3.1
20 */
21 abstract class JInstallerHelper
22 {
23 /**
24 * Downloads a package
25 *
26 * @param string $url URL of file to download
27 * @param mixed $target Download target filename or false to get the filename from the URL
28 *
29 * @return string|boolean Path to downloaded package or boolean false on failure
30 *
31 * @since 3.1
32 */
33 public static function downloadPackage($url, $target = false)
34 {
35 // Capture PHP errors
36 $track_errors = ini_get('track_errors');
37 ini_set('track_errors', true);
38
39 // Set user agent
40 $version = new JVersion;
41 ini_set('user_agent', $version->getUserAgent('Installer'));
42
43 // Load installer plugins, and allow URL and headers modification
44 $headers = array();
45 JPluginHelper::importPlugin('installer');
46 $dispatcher = JEventDispatcher::getInstance();
47 $dispatcher->trigger('onInstallerBeforePackageDownload', array(&$url, &$headers));
48
49 try
50 {
51 $response = JHttpFactory::getHttp()->get($url, $headers);
52 }
53 catch (RuntimeException $exception)
54 {
55 JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT', $exception->getMessage()), JLog::WARNING, 'jerror');
56
57 return false;
58 }
59
60 if (302 == $response->code && isset($response->headers['Location']))
61 {
62 return self::downloadPackage($response->headers['Location']);
63 }
64 elseif (200 != $response->code)
65 {
66 JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT', $response->code), JLog::WARNING, 'jerror');
67
68 return false;
69 }
70
71 // Parse the Content-Disposition header to get the file name
72 if (isset($response->headers['Content-Disposition'])
73 && preg_match("/\s*filename\s?=\s?(.*)/", $response->headers['Content-Disposition'], $parts))
74 {
75 $flds = explode(';', $parts[1]);
76 $target = trim($flds[0], '"');
77 }
78
79 $tmpPath = JFactory::getConfig()->get('tmp_path');
80
81 // Set the target path if not given
82 if (!$target)
83 {
84 $target = $tmpPath . '/' . self::getFilenameFromUrl($url);
85 }
86 else
87 {
88 $target = $tmpPath . '/' . basename($target);
89 }
90
91 // Write buffer to file
92 JFile::write($target, $response->body);
93
94 // Restore error tracking to what it was before
95 ini_set('track_errors', $track_errors);
96
97 // Bump the max execution time because not using built in php zip libs are slow
98 @set_time_limit(ini_get('max_execution_time'));
99
100 // Return the name of the downloaded package
101 return basename($target);
102 }
103
104 /**
105 * Unpacks a file and verifies it as a Joomla element package
106 * Supports .gz .tar .tar.gz and .zip
107 *
108 * @param string $p_filename The uploaded package filename or install directory
109 * @param boolean $alwaysReturnArray If should return false (and leave garbage behind) or return $retval['type']=false
110 *
111 * @return array|boolean Array on success or boolean false on failure
112 *
113 * @since 3.1
114 */
115 public static function unpack($p_filename, $alwaysReturnArray = false)
116 {
117 // Path to the archive
118 $archivename = $p_filename;
119
120 // Temporary folder to extract the archive into
121 $tmpdir = uniqid('install_');
122
123 // Clean the paths to use for archive extraction
124 $extractdir = JPath::clean(dirname($p_filename) . '/' . $tmpdir);
125 $archivename = JPath::clean($archivename);
126
127 // Do the unpacking of the archive
128 try
129 {
130 $extract = JArchive::extract($archivename, $extractdir);
131 }
132 catch (Exception $e)
133 {
134 if ($alwaysReturnArray)
135 {
136 return array(
137 'extractdir' => null,
138 'packagefile' => $archivename,
139 'type' => false,
140 );
141 }
142
143 return false;
144 }
145
146 if (!$extract)
147 {
148 if ($alwaysReturnArray)
149 {
150 return array(
151 'extractdir' => null,
152 'packagefile' => $archivename,
153 'type' => false,
154 );
155 }
156
157 return false;
158 }
159
160 /*
161 * Let's set the extraction directory and package file in the result array so we can
162 * cleanup everything properly later on.
163 */
164 $retval['extractdir'] = $extractdir;
165 $retval['packagefile'] = $archivename;
166
167 /*
168 * Try to find the correct install directory. In case the package is inside a
169 * subdirectory detect this and set the install directory to the correct path.
170 *
171 * List all the items in the installation directory. If there is only one, and
172 * it is a folder, then we will set that folder to be the installation folder.
173 */
174 $dirList = array_merge((array) JFolder::files($extractdir, ''), (array) JFolder::folders($extractdir, ''));
175
176 if (count($dirList) === 1)
177 {
178 if (JFolder::exists($extractdir . '/' . $dirList[0]))
179 {
180 $extractdir = JPath::clean($extractdir . '/' . $dirList[0]);
181 }
182 }
183
184 /*
185 * We have found the install directory so lets set it and then move on
186 * to detecting the extension type.
187 */
188 $retval['dir'] = $extractdir;
189
190 /*
191 * Get the extension type and return the directory/type array on success or
192 * false on fail.
193 */
194 $retval['type'] = self::detectType($extractdir);
195
196 if ($alwaysReturnArray || $retval['type'])
197 {
198 return $retval;
199 }
200 else
201 {
202 return false;
203 }
204 }
205
206 /**
207 * Method to detect the extension type from a package directory
208 *
209 * @param string $p_dir Path to package directory
210 *
211 * @return mixed Extension type string or boolean false on fail
212 *
213 * @since 3.1
214 */
215 public static function detectType($p_dir)
216 {
217 // Search the install dir for an XML file
218 $files = JFolder::files($p_dir, '\.xml$', 1, true);
219
220 if (!$files || !count($files))
221 {
222 JLog::add(JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'), JLog::WARNING, 'jerror');
223
224 return false;
225 }
226
227 foreach ($files as $file)
228 {
229 $xml = simplexml_load_file($file);
230
231 if (!$xml)
232 {
233 continue;
234 }
235
236 if ($xml->getName() !== 'extension')
237 {
238 unset($xml);
239 continue;
240 }
241
242 $type = (string) $xml->attributes()->type;
243
244 // Free up memory
245 unset($xml);
246
247 return $type;
248 }
249
250 JLog::add(JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'), JLog::WARNING, 'jerror');
251
252 // Free up memory.
253 unset($xml);
254
255 return false;
256 }
257
258 /**
259 * Gets a file name out of a url
260 *
261 * @param string $url URL to get name from
262 *
263 * @return mixed String filename or boolean false if failed
264 *
265 * @since 3.1
266 */
267 public static function getFilenameFromUrl($url)
268 {
269 if (is_string($url))
270 {
271 $parts = explode('/', $url);
272
273 return $parts[count($parts) - 1];
274 }
275
276 return false;
277 }
278
279 /**
280 * Clean up temporary uploaded package and unpacked extension
281 *
282 * @param string $package Path to the uploaded package file
283 * @param string $resultdir Path to the unpacked extension
284 *
285 * @return boolean True on success
286 *
287 * @since 3.1
288 */
289 public static function cleanupInstall($package, $resultdir)
290 {
291 $config = JFactory::getConfig();
292
293 // Does the unpacked extension directory exist?
294 if ($resultdir && is_dir($resultdir))
295 {
296 JFolder::delete($resultdir);
297 }
298
299 // Is the package file a valid file?
300 if (is_file($package))
301 {
302 JFile::delete($package);
303 }
304 elseif (is_file(JPath::clean($config->get('tmp_path') . '/' . $package)))
305 {
306 // It might also be just a base filename
307 JFile::delete(JPath::clean($config->get('tmp_path') . '/' . $package));
308 }
309 }
310
311 /**
312 * Splits contents of a sql file into array of discreet queries.
313 * Queries need to be delimited with end of statement marker ';'
314 *
315 * @param string $query The SQL statement.
316 *
317 * @return array Array of queries
318 *
319 * @since 3.1
320 * @deprecated 13.3 Use JDatabaseDriver::splitSql() directly
321 * @codeCoverageIgnore
322 */
323 public static function splitSql($query)
324 {
325 JLog::add('JInstallerHelper::splitSql() is deprecated. Use JDatabaseDriver::splitSql() instead.', JLog::WARNING, 'deprecated');
326 $db = JFactory::getDbo();
327
328 return $db->splitSql($query);
329 }
330 }
331