This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace AdminModule; |
||
4 | |||
5 | /** |
||
6 | * Description of UpdatePresenter |
||
7 | * |
||
8 | * @author Tomáš Voslař <tomas.voslar at webcook.cz> |
||
9 | */ |
||
10 | class UpdatePresenter extends \AdminModule\BasePresenter |
||
11 | { |
||
12 | 5 | protected function beforeRender() |
|
13 | { |
||
14 | 5 | parent::beforeRender(); |
|
15 | 5 | } |
|
16 | |||
17 | 6 | protected function startup() |
|
18 | { |
||
19 | 6 | parent::startup(); |
|
20 | 6 | } |
|
21 | |||
22 | 1 | public function renderDefault() |
|
23 | { |
||
24 | 1 | $this->reloadContent(); |
|
25 | |||
26 | 1 | $packages = \WebCMS\Helpers\SystemHelper::getPackages(); |
|
27 | |||
28 | 1 | foreach ($packages as &$package) { |
|
29 | if ($package['module']) { |
||
30 | $module = $this->createObject($package['package']); |
||
31 | |||
32 | $package['registered'] = $this->isRegistered($module->getName()); |
||
33 | } |
||
34 | 1 | } |
|
35 | |||
36 | 1 | $this->template->packages = $packages; |
|
0 ignored issues
–
show
|
|||
37 | 1 | } |
|
38 | |||
39 | 5 | public function handleUpdateSystem() |
|
40 | { |
||
41 | $installLog = './log/install.log'; |
||
42 | $installErrorLog = './log/install-error.log'; |
||
43 | |||
44 | putenv("COMPOSER_HOME=/usr/bin/.composer"); |
||
45 | |||
46 | exec("cd ../;git pull;composer update -n > $installLog 2> $installErrorLog"); |
||
47 | |||
48 | $successMessage = $this->getMessageFromFile('.'.$installLog); |
||
49 | |||
50 | if (strpos($successMessage, 'System has been updated.') !== FALSE) { |
||
51 | $this->flashMessage('System has been udpated.', 'success'); |
||
52 | } |
||
53 | |||
54 | $errorMessage = $this->getMessageFromFile('.'.$installErrorLog); |
||
55 | if (!empty($errorMessage)) { |
||
56 | $this->flashMessage('Error while updating system. Please contact administrator.', 'danger'); |
||
57 | } |
||
58 | |||
59 | $this->handleCheckUpdates(); |
||
60 | |||
61 | if (!$this->isAjax()) { |
||
62 | $this->forward('Update:'); |
||
63 | } else { |
||
64 | $this->invalidateControl('footer'); |
||
65 | 5 | } |
|
66 | } |
||
67 | |||
68 | /** |
||
69 | * @param string $file |
||
70 | */ |
||
71 | 5 | private function getMessageFromFile($file) |
|
72 | { |
||
73 | if (file_exists($file)) { |
||
74 | $message = file_get_contents($file); |
||
75 | 5 | } else { |
|
76 | $message = 'error'; |
||
77 | } |
||
78 | |||
79 | 5 | return $message; |
|
80 | 5 | } |
|
81 | |||
82 | 6 | public function actionClearCache() |
|
83 | 5 | { |
|
84 | 6 | $this->context->cacheStorage->clean(array(\Nette\Caching\Cache::ALL => TRUE)); |
|
0 ignored issues
–
show
The property
$context is declared private in Nette\Application\UI\Presenter . Since you implemented __get() , maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
85 | |||
86 | 6 | $this->flashMessage('Cache has been cleared.', 'success'); |
|
87 | 1 | $this->redirect("Update:functions"); |
|
88 | } |
||
89 | |||
90 | public function handleBackupDatabase() |
||
91 | { |
||
92 | $par = $this->context->getParameters(); |
||
0 ignored issues
–
show
The property
$context is declared private in Nette\Application\UI\Presenter . Since you implemented __get() , maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
93 | |||
94 | if (!file_exists('./upload/backups')) { |
||
95 | mkdir('./upload/backups'); |
||
96 | } |
||
97 | |||
98 | $user = $par['database']['user']; |
||
99 | $password = $par['database']['password']; |
||
100 | $database = $par['database']['dbname']; |
||
101 | |||
102 | exec("mysqldump -u $user -p$password $database > ./upload/backups/db-backup-".time().".sql"); |
||
103 | |||
104 | $this->flashMessage('Backup has been create. You can download this backup in filesystem - backup directory.', 'success'); |
||
105 | } |
||
106 | |||
107 | // REFACTOR |
||
108 | public function actionRegister($name) |
||
109 | { |
||
110 | $module = $this->createObject($name); |
||
111 | |||
112 | if (!$this->isRegistered($name)) { |
||
113 | $exists = $this->em->getRepository('WebCMS\Entity\Module')->findOneBy(array( |
||
114 | 'name' => $module->getName(), |
||
115 | )); |
||
116 | |||
117 | if (is_object($exists)) { |
||
118 | $exists->setActive(true); |
||
119 | } else { |
||
120 | $mod = new \WebCMS\Entity\Module(); |
||
121 | $mod->setName($module->getName()); |
||
122 | $mod->setPresenters($module->getPresenters()); |
||
123 | $mod->setActive(true); |
||
124 | |||
125 | $this->em->persist($mod); |
||
126 | } |
||
127 | |||
128 | $this->em->flush(); |
||
129 | $this->copyTemplates($name); |
||
130 | $this->flashMessage('Module has been registered.', 'success'); |
||
131 | } else { |
||
132 | $this->flashMessage('Module is already registered.', 'danger'); |
||
133 | } |
||
134 | |||
135 | $this->forward('default'); |
||
136 | } |
||
137 | |||
138 | 6 | private function copyTemplates($name) |
|
139 | { |
||
140 | if (!file_exists('../app/templates/'.$name)) { |
||
141 | mkdir('../app/templates/'.$name); |
||
142 | 6 | } |
|
143 | exec('cp -r ../libs/webcms2/'.$name.'/Frontend/templatesDefault/* ../app/templates/'.$name); |
||
144 | } |
||
145 | |||
146 | 6 | public function actionUnregister($name) |
|
147 | 6 | { |
|
148 | $module = $this->createObject($name); |
||
149 | $module = $this->em->getRepository('WebCMS\Entity\Module')->findOneBy(array( |
||
150 | 'name' => $module->getName(), |
||
151 | )); |
||
152 | |||
153 | $module->setActive(false); |
||
154 | $this->em->flush(); |
||
155 | |||
156 | $this->flashMessage('Module has been unregistered from system.', 'success'); |
||
157 | $this->forward('default'); |
||
158 | } |
||
159 | |||
160 | View Code Duplication | private function isRegistered($name) |
|
161 | { |
||
162 | $exists = $this->em->getRepository('WebCMS\Entity\Module')->findOneBy(array( |
||
163 | 'name' => $name, |
||
164 | )); |
||
165 | |||
166 | if (is_object($exists) && $exists->getActive()) { |
||
167 | return TRUE; |
||
168 | } else { |
||
169 | return FALSE; |
||
170 | } |
||
171 | } |
||
172 | |||
173 | public function handleCheckUpdates() |
||
174 | { |
||
175 | $client = new \Packagist\Api\Client(); |
||
176 | |||
177 | $packages = \WebCMS\Helpers\SystemHelper::getPackages(); |
||
178 | |||
179 | $needUpdateCount = 0; |
||
180 | foreach ($packages as &$package) { |
||
181 | if ($package['vendor'] === 'webcms2') { |
||
182 | $apiResult = $client->get($package['vendor'].'/'.$package['package']); |
||
183 | $versions = $apiResult->getVersions(); |
||
184 | |||
185 | $devVersion = $versions[$package['version']]; |
||
186 | if (count($versions) > 1) { |
||
187 | $newestVersion = next($versions); |
||
188 | $newestVersion = $newestVersion->getVersion(); |
||
189 | while (strpos($newestVersion, 'dev') !== false) { |
||
190 | $newestVersion = next($versions); |
||
191 | $newestVersion = $newestVersion->getVersion(); |
||
192 | } |
||
193 | } else { |
||
194 | $newestVersion = null; |
||
195 | } |
||
196 | |||
197 | // development or production version? |
||
198 | if (strpos($package['version'], 'dev') !== false) { |
||
199 | if ($package['versionHash'] !== mb_substr($devVersion->getSource()->getReference(), 0, 7)) { |
||
200 | $needUpdateCount++; |
||
201 | } |
||
202 | } else { |
||
203 | if ($package['version'] !== $newestVersion) { |
||
204 | $needUpdateCount++; |
||
205 | } |
||
206 | } |
||
207 | } |
||
208 | } |
||
209 | |||
210 | $nuc = $this->settings->get('needUpdateCount', 'system', 'text'); |
||
211 | |||
212 | $setting = $this->em->find('WebCMS\Entity\Setting', $nuc->getId()); |
||
213 | $setting->setValue($needUpdateCount); |
||
214 | |||
215 | if ($needUpdateCount > 0) { |
||
216 | $this->flashMessage('Available new updates.', 'success'); |
||
217 | } else { |
||
218 | $this->flashMessage('Your system is up to date.', 'success'); |
||
219 | } |
||
220 | |||
221 | $this->em->flush(); |
||
222 | |||
223 | $this->invalidateControl('header'); |
||
224 | } |
||
225 | |||
226 | public function handleDeleteModule($name) |
||
227 | { |
||
228 | $config = json_decode(file_get_contents('../composer.json')); |
||
229 | |||
230 | if (!empty($config->require->$name)) { |
||
231 | unset($config->require->$name); |
||
232 | } |
||
233 | |||
234 | file_put_contents('../composer.json', json_encode($config, JSON_PRETTY_PRINT)); |
||
235 | |||
236 | $installLog = './log/install.log'; |
||
237 | $installErrorLog = './log/install-error.log'; |
||
238 | |||
239 | putenv("COMPOSER_HOME=/usr/bin/.composer"); |
||
240 | exec("rm -rf ../app/proxies/*"); |
||
241 | exec("cd ../;composer update $name -n > $installLog 2> $installErrorLog"); |
||
242 | exec("cd ../;composer dumpautoload"); |
||
243 | exec("./libs/webcms2/webcms2/install/install.sh 3"); |
||
244 | |||
245 | $this->forward('default'); |
||
246 | } |
||
247 | |||
248 | 2 | public function actionAddModule() |
|
249 | { |
||
250 | 2 | } |
|
251 | |||
252 | 1 | public function renderAddModule() |
|
253 | { |
||
254 | 1 | $this->reloadModalContent(); |
|
255 | 1 | } |
|
256 | |||
257 | 1 | public function createComponentAddModuleForm() |
|
258 | { |
||
259 | 1 | $form = $this->createForm(); |
|
260 | |||
261 | 1 | $form->addSelect('module', 'Module', $this->getModulesToInstall())->setAttribute('class', 'form-control'); |
|
0 ignored issues
–
show
'form-control' is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
262 | 1 | $form->addText('version', 'Module version')->setDefaultValue('0.*')->setAttribute('class', 'form-control'); |
|
0 ignored issues
–
show
'form-control' is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
263 | |||
264 | 1 | $form->addSubmit('install', 'Install module'); |
|
265 | 1 | $form->onSuccess[] = callback($this, 'addModuleFormSubmitted'); |
|
266 | |||
267 | 1 | return $form; |
|
268 | } |
||
269 | |||
270 | public function addModuleFormSubmitted($form) |
||
271 | { |
||
272 | $values = $form->getValues(); |
||
273 | |||
274 | putenv("COMPOSER_HOME=/usr/bin/.composer"); |
||
275 | |||
276 | $installLog = './log/install.log'; |
||
277 | $installErrorLog = './log/install-error.log'; |
||
278 | |||
279 | $module = $values->module; |
||
280 | $version = $values->version; |
||
281 | exec("cd ../;composer require $module $version --no-update > $installLog 2> $installErrorLog"); |
||
282 | exec("cd ../;composer update > $installLog 2> $installErrorLog"); |
||
283 | exec("../libs/webcms2/webcms2/install/install.sh 3; >"); |
||
284 | |||
285 | $this->forward('default'); |
||
286 | } |
||
287 | |||
288 | 1 | private function getModulesToInstall() |
|
289 | { |
||
290 | 1 | $packages = \WebCMS\Helpers\SystemHelper::getPackages(); |
|
291 | |||
292 | 1 | $client = new \Packagist\Api\Client(); |
|
293 | 1 | $apiResult = $client->search('webcms2/*'); |
|
294 | |||
295 | 1 | $notInstalled = array(); |
|
296 | 1 | foreach ($apiResult as $package) { |
|
297 | 1 | if (!array_key_exists($package->getName(), $packages)) { |
|
298 | 1 | $notInstalled[$package->getName()] = $package->getName(); |
|
299 | 1 | } |
|
300 | 1 | } |
|
301 | |||
302 | 1 | return $notInstalled; |
|
303 | } |
||
304 | |||
305 | 1 | public function renderFunctions() |
|
306 | { |
||
307 | 1 | $this->reloadContent(); |
|
308 | 1 | } |
|
309 | |||
310 | 1 | public function renderCreateModule() |
|
311 | { |
||
312 | 1 | $this->reloadModalContent(); |
|
313 | 1 | } |
|
314 | |||
315 | 1 | View Code Duplication | public function createComponentCreateModuleForm() |
316 | { |
||
317 | 1 | $form = $this->createForm(); |
|
318 | |||
319 | 1 | $form->addText('name', 'Name')->setAttribute('class', 'form-control'); |
|
0 ignored issues
–
show
'form-control' is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
320 | 1 | $form->addText('author', 'Author')->setAttribute('class', 'form-control'); |
|
0 ignored issues
–
show
'form-control' is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
321 | 1 | $form->addText('email', 'Author\'s email')->setAttribute('class', 'form-control'); |
|
0 ignored issues
–
show
'form-control' is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
322 | 1 | $form->addTextArea('description', 'Description')->setAttribute('class', 'form-control'); |
|
0 ignored issues
–
show
'form-control' is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
323 | |||
324 | 1 | $form->addSubmit('install', 'Create new module')->setAttribute('class', 'btn btn-primary'); |
|
0 ignored issues
–
show
'btn btn-primary' is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
325 | 1 | $form->onSuccess[] = callback($this, 'createModuleFormSubmitted'); |
|
326 | |||
327 | 1 | return $form; |
|
328 | } |
||
329 | |||
330 | public function createModuleFormSubmitted($form) |
||
331 | { |
||
332 | $values = $form->getValues(); |
||
333 | |||
334 | $name = strtolower(trim($values->name)); |
||
335 | $nameBig = ucfirst($name); |
||
336 | $author = trim($values->author); |
||
337 | $email = trim($values->email); |
||
338 | $description = trim($values->description); |
||
339 | |||
340 | exec("cd ../libs/webcms2/webcms2/install;./module.sh create $name $nameBig '$author' '$email' '$description' > ../../../../log/install.log 2> ../../../../log/install-error.log"); |
||
341 | |||
342 | $this->flashMessage('Module has been created.', 'success'); |
||
343 | $this->forward('default'); |
||
344 | } |
||
345 | |||
346 | // render log tab |
||
347 | 1 | public function renderLog() |
|
348 | { |
||
349 | 1 | $this->reloadContent(); |
|
350 | |||
351 | 1 | $this->template->installLog = $this->getLog('../log/install.log'); |
|
0 ignored issues
–
show
The property
$template is declared private in Nette\Application\UI\Control . Since you implemented __get() , maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
352 | 1 | $this->template->installErrorLog = $this->getLog('../log/install-error.log'); |
|
0 ignored issues
–
show
The property
$template is declared private in Nette\Application\UI\Control . Since you implemented __get() , maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
353 | 1 | $this->template->updateLog = $this->getLog('../log/auto-update.log'); |
|
0 ignored issues
–
show
The property
$template is declared private in Nette\Application\UI\Control . Since you implemented __get() , maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
354 | 1 | $this->template->errorLog = $this->getLog('../log/error.log'); |
|
0 ignored issues
–
show
The property
$template is declared private in Nette\Application\UI\Control . Since you implemented __get() , maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.
Since your code implements the magic setter <?php
/**
* @property int $x
* @property int $y
* @property string $text
*/
class MyLabel
{
private $properties;
private $allowedProperties = array('x', 'y', 'text');
public function __get($name)
{
if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
return $properties[$name];
} else {
return null;
}
}
public function __set($name, $value)
{
if (in_array($name, $this->allowedProperties)) {
$properties[$name] = $value;
} else {
throw new \LogicException("Property $name is not defined.");
}
}
}
Since the property has write access only, you can use the @property-write annotation instead. Of course, you may also just have mistyped another name, in which case you should fix the error. See also the PhpDoc documentation for @property.
Loading history...
|
|||
355 | 1 | } |
|
356 | |||
357 | /** |
||
358 | * @param string $path |
||
359 | */ |
||
360 | 1 | private function getLog($path) |
|
361 | { |
||
362 | 1 | if (file_exists($path)) { |
|
363 | return nl2br(file_get_contents($path)); |
||
364 | } |
||
365 | |||
366 | 1 | return ''; |
|
367 | } |
||
368 | } |
||
369 |
Since your code implements the magic setter
_set
, this function will be called for any write access on an undefined variable. You can add the@property
annotation to your class or interface to document the existence of this variable.Since the property has write access only, you can use the @property-write annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.