Attempt to fix paging on contact summary
[civicrm-core.git] / CRM / Extension / Downloader.php
... / ...
CommitLineData
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28/**
29 * This class handles downloads of remotely-provided extensions
30 *
31 * @package CRM
32 * @copyright CiviCRM LLC (c) 2004-2019
33 */
34class CRM_Extension_Downloader {
35 /**
36 * @var CRM_Extension_Container_Basic the place where downloaded extensions are ultimately stored
37 */
38 public $container;
39
40 /**
41 * @var string local path to a temporary data directory
42 */
43 public $tmpDir;
44
45 /**
46 * @param CRM_Extension_Manager $manager
47 * @param string $containerDir
48 * The place to store downloaded & extracted extensions.
49 * @param string $tmpDir
50 */
51 public function __construct(CRM_Extension_Manager $manager, $containerDir, $tmpDir) {
52 $this->manager = $manager;
53 $this->containerDir = $containerDir;
54 $this->tmpDir = $tmpDir;
55 }
56
57 /**
58 * Determine whether downloading is supported.
59 *
60 * @param \CRM_EXtension_Info $extensionInfo Optional info for (updated) extension
61 *
62 * @return array
63 * list of error messages; empty if OK
64 */
65 public function checkRequirements($extensionInfo = NULL) {
66 $errors = array();
67
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}");
71 $errors[] = array(
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.",
74 array(
75 1 => $url,
76 )
77 ),
78 );
79 }
80
81 if (!class_exists('ZipArchive')) {
82 $errors[] = array(
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.'),
85 );
86 }
87
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.');
91 }
92
93 if ($extensionInfo) {
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) {
97 $errors[] = [
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]),
100 ];
101 }
102 }
103 }
104
105 return $errors;
106 }
107
108 /**
109 * Install or upgrade an extension from a remote URL.
110 *
111 * @param string $key
112 * The name of the extension being installed.
113 * @param string $downloadUrl
114 * URL of a .zip file.
115 * @return bool
116 * TRUE for success
117 * @throws CRM_Extension_Exception
118 */
119 public function download($key, $downloadUrl) {
120 $filename = $this->tmpDir . DIRECTORY_SEPARATOR . $key . '.zip';
121 $destDir = $this->containerDir . DIRECTORY_SEPARATOR . $key;
122
123 if (!$downloadUrl) {
124 CRM_Core_Error::fatal('Cannot install this extension - downloadUrl is not set!');
125 }
126
127 if (!$this->fetch($downloadUrl, $filename)) {
128 return FALSE;
129 }
130
131 $extractedZipPath = $this->extractFiles($key, $filename);
132 if (!$extractedZipPath) {
133 return FALSE;
134 }
135
136 if (!$this->validateFiles($key, $extractedZipPath)) {
137 return FALSE;
138 }
139
140 $this->manager->replace($extractedZipPath);
141
142 return TRUE;
143 }
144
145 /**
146 * Download the remote zipfile.
147 *
148 * @param string $remoteFile
149 * URL of a .zip file.
150 * @param string $localFile
151 * Path at which to store the .zip file.
152 * @return bool
153 * Whether the download was successful.
154 */
155 public function fetch($remoteFile, $localFile) {
156 $result = CRM_Utils_HttpClient::singleton()->fetch($remoteFile, $localFile);
157 switch ($result) {
158 case CRM_Utils_HttpClient::STATUS_OK:
159 return TRUE;
160
161 default:
162 return FALSE;
163 }
164 }
165
166 /**
167 * Extract an extension from a zip file.
168 *
169 * @param string $key
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
174 * zip file path
175 */
176 public function extractFiles($key, $zipFile) {
177 $config = CRM_Core_Config::singleton();
178
179 $zip = new ZipArchive();
180 $res = $zip->open($zipFile);
181 if ($res === TRUE) {
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');
185 return FALSE;
186 }
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');
191 return FALSE;
192 }
193 }
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');
196 return FALSE;
197 }
198 $zip->close();
199 }
200 else {
201 CRM_Core_Session::setStatus(ts('Unable to extract the extension.'), '', 'error');
202 return FALSE;
203 }
204
205 return $extractedZipPath;
206 }
207
208 /**
209 * Validate that $extractedZipPath contains valid for extension $key
210 *
211 * @param $key
212 * @param $extractedZipPath
213 *
214 * @return bool
215 */
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');
220 return FALSE;
221 }
222
223 try {
224 $newInfo = CRM_Extension_Info::loadFromFile($filename);
225 }
226 catch (Exception $e) {
227 CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
228 return FALSE;
229 }
230
231 if ($newInfo->key != $key) {
232 CRM_Core_Error::fatal('Cannot install - there are differences between extdir XML file and archive XML file!');
233 }
234
235 return TRUE;
236 }
237
238}