3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * This class handles downloads of remotely-provided extensions
32 * @copyright CiviCRM LLC (c) 2004-2019
34 class CRM_Extension_Downloader
{
36 * @var CRM_Extension_Container_Basic the place where downloaded extensions are ultimately stored
41 * @var string local path to a temporary data directory
46 * @param CRM_Extension_Manager $manager
47 * @param string $containerDir
48 * The place to store downloaded & extracted extensions.
49 * @param string $tmpDir
51 public function __construct(CRM_Extension_Manager
$manager, $containerDir, $tmpDir) {
52 $this->manager
= $manager;
53 $this->containerDir
= $containerDir;
54 $this->tmpDir
= $tmpDir;
58 * Determine whether downloading is supported.
60 * @param \CRM_EXtension_Info $extensionInfo Optional info for (updated) extension
63 * list of error messages; empty if OK
65 public function checkRequirements($extensionInfo = NULL) {
68 if (!$this->containerDir ||
!is_dir($this->containerDir
) ||
!is_writable($this->containerDir
)) {
69 $civicrmDestination = urlencode(CRM_Utils_System
::url('civicrm/admin/extensions', 'reset=1'));
70 $url = CRM_Utils_System
::url('civicrm/admin/setting/path', "reset=1&civicrmDestination=${civicrmDestination}");
72 'title' => ts('Directory Unwritable'),
73 'message' => ts("Your extensions directory is not set or is not writable. Click <a href='%1'>here</a> to set the extensions directory.",
81 if (!class_exists('ZipArchive')) {
83 'title' => ts('ZIP Support Required'),
84 'message' => ts('You will not be able to install extensions at this time because your installation of PHP does not support ZIP archives. Please ask your system administrator to install the standard PHP-ZIP extension.'),
88 if (empty($errors) && !CRM_Utils_HttpClient
::singleton()->isRedirectSupported()) {
89 CRM_Core_Session
::setStatus(ts('WARNING: The downloader may be unable to download files which require HTTP redirection. This may be a configuration issue with PHP\'s open_basedir or safe_mode.'));
90 CRM_Core_Error
::debug_log_message('WARNING: The downloader may be unable to download files which require HTTP redirection. This may be a configuration issue with PHP\'s open_basedir or safe_mode.');
94 $requiredExtensions = CRM_Extension_System
::singleton()->getManager()->findInstallRequirements([$extensionInfo->key
], $extensionInfo);
95 foreach ($requiredExtensions as $extension) {
96 if (CRM_Extension_System
::singleton()->getManager()->getStatus($extension) !== CRM_Extension_Manager
::STATUS_INSTALLED
&& $extension !== $extensionInfo->key
) {
98 'title' => ts('Missing Requirement: %1', [1 => $extension]),
99 'message' => ts('You will not be able to install/upgrade %1 until you have installed the %2 extension.', [1 => $extensionInfo->key
, 2 => $extension]),
109 * Install or upgrade an extension from a remote URL.
112 * The name of the extension being installed.
113 * @param string $downloadUrl
114 * URL of a .zip file.
117 * @throws CRM_Extension_Exception
119 public function download($key, $downloadUrl) {
120 $filename = $this->tmpDir
. DIRECTORY_SEPARATOR
. $key . '.zip';
121 $destDir = $this->containerDir
. DIRECTORY_SEPARATOR
. $key;
124 CRM_Core_Error
::fatal('Cannot install this extension - downloadUrl is not set!');
127 if (!$this->fetch($downloadUrl, $filename)) {
131 $extractedZipPath = $this->extractFiles($key, $filename);
132 if (!$extractedZipPath) {
136 if (!$this->validateFiles($key, $extractedZipPath)) {
140 $this->manager
->replace($extractedZipPath);
146 * Download the remote zipfile.
148 * @param string $remoteFile
149 * URL of a .zip file.
150 * @param string $localFile
151 * Path at which to store the .zip file.
153 * Whether the download was successful.
155 public function fetch($remoteFile, $localFile) {
156 $result = CRM_Utils_HttpClient
::singleton()->fetch($remoteFile, $localFile);
158 case CRM_Utils_HttpClient
::STATUS_OK
:
167 * Extract an extension from a zip file.
170 * The name of the extension being installed; this usually matches the basedir in the .zip.
171 * @param string $zipFile
172 * The local path to a .zip file.
173 * @return string|FALSE
176 public function extractFiles($key, $zipFile) {
177 $config = CRM_Core_Config
::singleton();
179 $zip = new ZipArchive();
180 $res = $zip->open($zipFile);
182 $zipSubDir = CRM_Utils_Zip
::guessBasedir($zip, $key);
183 if ($zipSubDir === FALSE) {
184 CRM_Core_Session
::setStatus(ts('Unable to extract the extension: bad directory structure'), '', 'error');
187 $extractedZipPath = $this->tmpDir
. DIRECTORY_SEPARATOR
. $zipSubDir;
188 if (is_dir($extractedZipPath)) {
189 if (!CRM_Utils_File
::cleanDir($extractedZipPath, TRUE, FALSE)) {
190 CRM_Core_Session
::setStatus(ts('Unable to extract the extension: %1 cannot be cleared', array(1 => $extractedZipPath)), ts('Installation Error'), 'error');
194 if (!$zip->extractTo($this->tmpDir
)) {
195 CRM_Core_Session
::setStatus(ts('Unable to extract the extension to %1.', array(1 => $this->tmpDir
)), ts('Installation Error'), 'error');
201 CRM_Core_Session
::setStatus(ts('Unable to extract the extension.'), '', 'error');
205 return $extractedZipPath;
209 * Validate that $extractedZipPath contains valid for extension $key
212 * @param $extractedZipPath
216 public function validateFiles($key, $extractedZipPath) {
217 $filename = $extractedZipPath . DIRECTORY_SEPARATOR
. CRM_Extension_Info
::FILENAME
;
218 if (!is_readable($filename)) {
219 CRM_Core_Session
::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
224 $newInfo = CRM_Extension_Info
::loadFromFile($filename);
226 catch (Exception
$e) {
227 CRM_Core_Session
::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
231 if ($newInfo->key
!= $key) {
232 CRM_Core_Error
::fatal('Cannot install - there are differences between extdir XML file and archive XML file!');