phpcs - Fix error, "Visibility must be declared on method"
[civicrm-core.git] / CRM / Extension / Downloader.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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-2014
33 * $Id$
34 *
35 */
36 class CRM_Extension_Downloader {
37 /**
38 * @var CRM_Extension_Container_Basic the place where downloaded extensions are ultimately stored
39 */
40 public $container;
41
42 /**
43 * @var string local path to a temporary data directory
44 */
45 public $tmpDir;
46
47 /**
48 * @param CRM_Extension_Manager $manager
49 * @param string $containerDir the place to store downloaded & extracted extensions
50 * @param string $tmpDir
51 */
52 public function __construct(CRM_Extension_Manager $manager, $containerDir, $tmpDir) {
53 $this->manager = $manager;
54 $this->containerDir = $containerDir;
55 $this->tmpDir = $tmpDir;
56 }
57
58 /**
59 * Determine whether downloading is supported
60 *
61 * @return array list of error messages; empty if OK
62 */
63 public function checkRequirements() {
64 $errors = array();
65
66 if (!$this->containerDir || !is_dir($this->containerDir) || !is_writeable($this->containerDir)) {
67 $civicrmDestination = urlencode(CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1'));
68 $url = CRM_Utils_System::url('civicrm/admin/setting/path', "reset=1&civicrmDestination=${civicrmDestination}");
69 $errors[] = array(
70 'title' => ts('Directory Unwritable'),
71 //'message' => ts('Your extensions directory: %1 is not web server writable. Please go to the <a href="%2">path setting page</a> and correct it.<br/>',
72 'message' => ts("Your extensions directory is not set or is not writable. Click <a href='%1'>here</a> to set the extensions directory.",
73 array(
74 //1 => $this->containerDir,
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 return $errors;
94 }
95
96 /**
97 * Install or upgrade an extension from a remote URL
98 *
99 * @param string $key the name of the extension being installed
100 * @param string $downloadUrl URL of a .zip file
101 * @return bool TRUE for success
102 * @throws CRM_Extension_Exception
103 */
104 public function download($key, $downloadUrl) {
105 $filename = $this->tmpDir . DIRECTORY_SEPARATOR . $key . '.zip';
106 $destDir = $this->containerDir . DIRECTORY_SEPARATOR . $key;
107
108 if (!$downloadUrl) {
109 CRM_Core_Error::fatal('Cannot install this extension - downloadUrl is not set!');
110 }
111
112 if (! $this->fetch($downloadUrl, $filename)) {
113 return FALSE;
114 }
115
116 $extractedZipPath = $this->extractFiles($key, $filename);
117 if (! $extractedZipPath) {
118 return FALSE;
119 }
120
121 if (! $this->validateFiles($key, $extractedZipPath)) {
122 return FALSE;
123 }
124
125 $this->manager->replace($extractedZipPath);
126
127 return TRUE;
128 }
129
130 /**
131 * Download the remote zipfile.
132 *
133 * @param string $remoteFile URL of a .zip file
134 * @param string $localFile path at which to store the .zip file
135 * @return boolean Whether the download was successful.
136 */
137 public function fetch($remoteFile, $localFile) {
138 $result = CRM_Utils_HttpClient::singleton()->fetch($remoteFile, $localFile);
139 switch ($result) {
140 case CRM_Utils_HttpClient::STATUS_OK:
141 return TRUE;
142 default:
143 return FALSE;
144 }
145 }
146
147 /**
148 * Extract an extension from a zip file
149 *
150 * @param string $key the name of the extension being installed; this usually matches the basedir in the .zip
151 * @param string $zipFile the local path to a .zip file
152 * @return string|FALSE zip file path
153 */
154 public function extractFiles($key, $zipFile) {
155 $config = CRM_Core_Config::singleton();
156
157 $zip = new ZipArchive();
158 $res = $zip->open($zipFile);
159 if ($res === TRUE) {
160 $zipSubDir = CRM_Utils_Zip::guessBasedir($zip, $key);
161 if ($zipSubDir === FALSE) {
162 CRM_Core_Session::setStatus(ts('Unable to extract the extension: bad directory structure'), '', 'error');
163 return FALSE;
164 }
165 $extractedZipPath = $this->tmpDir . DIRECTORY_SEPARATOR . $zipSubDir;
166 if (is_dir($extractedZipPath)) {
167 if (!CRM_Utils_File::cleanDir($extractedZipPath, TRUE, FALSE)) {
168 CRM_Core_Session::setStatus(ts('Unable to extract the extension: %1 cannot be cleared', array(1 => $extractedZipPath)), ts('Installation Error'), 'error');
169 return FALSE;
170 }
171 }
172 if (!$zip->extractTo($this->tmpDir)) {
173 CRM_Core_Session::setStatus(ts('Unable to extract the extension to %1.', array(1 => $this->tmpDir)), ts('Installation Error'), 'error');
174 return FALSE;
175 }
176 $zip->close();
177 }
178 else {
179 CRM_Core_Session::setStatus(ts('Unable to extract the extension.'), '', 'error');
180 return FALSE;
181 }
182
183 return $extractedZipPath;
184 }
185
186 /**
187 * Validate that $extractedZipPath contains valid for extension $key
188 *
189 * @param $key
190 * @param $extractedZipPath
191 *
192 * @return bool
193 */
194 public function validateFiles($key, $extractedZipPath) {
195 $filename = $extractedZipPath . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME;
196 if (!is_readable($filename)) {
197 CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
198 return FALSE;
199 }
200
201 try {
202 $newInfo = CRM_Extension_Info::loadFromFile($filename);
203 } catch (Exception $e) {
204 CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
205 return FALSE;
206 }
207
208 if ($newInfo->key != $key) {
209 CRM_Core_Error::fatal('Cannot install - there are differences between extdir XML file and archive XML file!');
210 }
211
212 return TRUE;
213 }
214
215 }