1 <?php
2 /**
3 * @package FrameworkOnFramework
4 * @subpackage utils
5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
6 * @license GNU General Public License version 2 or later; see LICENSE.txt
7 */
8
9 defined('FOF_INCLUDED') or die;
10
11 /**
12 * A utility class to check that your extension's files are not missing and have not been tampered with.
13 *
14 * You need a file called fileslist.php in your component's administrator root directory with the following contents:
15 *
16 * $phpFileChecker = array(
17 * 'version' => 'revCEE2DAB',
18 * 'date' => '2014-10-16',
19 * 'directories' => array(
20 * 'administrator/components/com_foobar',
21 * ....
22 * ),
23 * 'files' => array(
24 * 'administrator/components/com_foobar/access.xml' => array('705', '09aa0351a316bf011ecc8c1145134761', 'b95f00c7b49a07a60570dc674f2497c45c4e7152'),
25 * ....
26 * )
27 * );
28 *
29 * All directory and file paths are relative to the site's root
30 *
31 * The directories array is a list of
32 */
33 class FOFUtilsFilescheck
34 {
35 /** @var string The name of the component */
36 protected $option = '';
37
38 /** @var string Current component version */
39 protected $version = null;
40
41 /** @var string Current component release date */
42 protected $date = null;
43
44 /** @var array List of files to check as filepath => (filesize, md5, sha1) */
45 protected $fileList = array();
46
47 /** @var array List of directories to check that exist */
48 protected $dirList = array();
49
50 /** @var bool Is the reported component version different than the version of the #__extensions table? */
51 protected $wrongComponentVersion = false;
52
53 /** @var bool Is the fileslist.php reporting a version different than the reported component version? */
54 protected $wrongFilesVersion = false;
55
56 /**
57 * Create and initialise the object
58 *
59 * @param string $option Component name, e.g. com_foobar
60 * @param string $version The current component version, as reported by the component
61 * @param string $date The current component release date, as reported by the component
62 */
63 public function __construct($option, $version, $date)
64 {
65 // Initialise from parameters
66 $this->option = $option;
67 $this->version = $version;
68 $this->date = $date;
69
70 // Retrieve the date and version from the #__extensions table
71 $db = FOFPlatform::getInstance()->getDbo();
72 $query = $db->getQuery(true)->select('*')->from($db->qn('#__extensions'))
73 ->where($db->qn('element') . ' = ' . $db->q($this->option))
74 ->where($db->qn('type') . ' = ' . $db->q('component'));
75 $extension = $db->setQuery($query)->loadObject();
76
77 // Check the version and date against those from #__extensions. I hate heavily nested IFs as much as the next
78 // guy, but what can you do...
79 if (!is_null($extension))
80 {
81 $manifestCache = $extension->manifest_cache;
82
83 if (!empty($manifestCache))
84 {
85 $manifestCache = json_decode($manifestCache, true);
86
87 if (is_array($manifestCache) && isset($manifestCache['creationDate']) && isset($manifestCache['version']))
88 {
89 // Make sure the fileslist.php version and date match the component's version
90 if ($this->version != $manifestCache['version'])
91 {
92 $this->wrongComponentVersion = true;
93 }
94
95 if ($this->date != $manifestCache['creationDate'])
96 {
97 $this->wrongComponentVersion = true;
98 }
99 }
100 }
101 }
102
103 // Try to load the fileslist.php file from the component's back-end root
104 $filePath = JPATH_ADMINISTRATOR . '/components/' . $this->option . '/fileslist.php';
105
106 if (!file_exists($filePath))
107 {
108 return;
109 }
110
111 include $filePath;
112
113 // Make sure the fileslist.php version and date match the component's version
114 if ($this->version != $phpFileChecker['version'])
115 {
116 $this->wrongFilesVersion = true;
117 }
118
119 if ($this->date != $phpFileChecker['date'])
120 {
121 $this->wrongFilesVersion = true;
122 }
123
124 // Initialise the files and directories lists
125 $this->fileList = $phpFileChecker['files'];
126 $this->dirList = $phpFileChecker['directories'];
127 }
128
129 /**
130 * Is the reported component version different than the version of the #__extensions table?
131 *
132 * @return boolean
133 */
134 public function isWrongComponentVersion()
135 {
136 return $this->wrongComponentVersion;
137 }
138
139 /**
140 * Is the fileslist.php reporting a version different than the reported component version?
141 *
142 * @return boolean
143 */
144 public function isWrongFilesVersion()
145 {
146 return $this->wrongFilesVersion;
147 }
148
149 /**
150 * Performs a fast check of file and folders. If even one of the files/folders doesn't exist, or even one file has
151 * the wrong file size it will return false.
152 *
153 * @return bool False when there are mismatched files and directories
154 */
155 public function fastCheck()
156 {
157 // Check that all directories exist
158 foreach ($this->dirList as $directory)
159 {
160 $directory = JPATH_ROOT . '/' . $directory;
161
162 if (!@is_dir($directory))
163 {
164 return false;
165 }
166 }
167
168 // Check that all files exist and have the right size
169 foreach ($this->fileList as $filePath => $fileData)
170 {
171 $filePath = JPATH_ROOT . '/' . $filePath;
172
173 if (!@file_exists($filePath))
174 {
175 return false;
176 }
177
178 $fileSize = @filesize($filePath);
179
180 if ($fileSize != $fileData[0])
181 {
182 return false;
183 }
184 }
185
186 return true;
187 }
188
189 /**
190 * Performs a slow, thorough check of all files and folders (including MD5/SHA1 sum checks)
191 *
192 * @param int $idx The index from where to start
193 *
194 * @return array Progress report
195 */
196 public function slowCheck($idx = 0)
197 {
198 $ret = array(
199 'done' => false,
200 'files' => array(),
201 'folders' => array(),
202 'idx' => $idx
203 );
204
205 $totalFiles = count($this->fileList);
206 $totalFolders = count($this->dirList);
207 $fileKeys = array_keys($this->fileList);
208
209 $timer = new FOFUtilsTimer(3.0, 75.0);
210
211 while ($timer->getTimeLeft() && (($idx < $totalFiles) || ($idx < $totalFolders)))
212 {
213 if ($idx < $totalFolders)
214 {
215 $directory = JPATH_ROOT . '/' . $this->dirList[$idx];
216
217 if (!@is_dir($directory))
218 {
219 $ret['folders'][] = $directory;
220 }
221 }
222
223 if ($idx < $totalFiles)
224 {
225 $fileKey = $fileKeys[$idx];
226 $filePath = JPATH_ROOT . '/' . $fileKey;
227 $fileData = $this->fileList[$fileKey];
228
229 if (!@file_exists($filePath))
230 {
231 $ret['files'][] = $fileKey . ' (missing)';
232 }
233 elseif (@filesize($filePath) != $fileData[0])
234 {
235 $ret['files'][] = $fileKey . ' (size ' . @filesize($filePath) . ' ≠ ' . $fileData[0] . ')';
236 }
237 else
238 {
239 if (function_exists('sha1_file'))
240 {
241 $fileSha1 = @sha1_file($filePath);
242
243 if ($fileSha1 != $fileData[2])
244 {
245 $ret['files'][] = $fileKey . ' (SHA1 ' . $fileSha1 . ' ≠ ' . $fileData[2] . ')';
246 }
247 }
248 elseif (function_exists('md5_file'))
249 {
250 $fileMd5 = @md5_file($filePath);
251
252 if ($fileMd5 != $fileData[1])
253 {
254 $ret['files'][] = $fileKey . ' (MD5 ' . $fileMd5 . ' ≠ ' . $fileData[1] . ')';
255 }
256 }
257 }
258 }
259
260 $idx++;
261 }
262
263 if (($idx >= $totalFiles) && ($idx >= $totalFolders))
264 {
265 $ret['done'] = true;
266 }
267
268 $ret['idx'] = $idx;
269
270 return $ret;
271 }
272 }