plugins.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  1. <?php
  2. /*
  3. * phpMeccano v0.2.0. Web-framework written with php programming language. Core module [plugins.php].
  4. * Copyright (C) 2015-2019 Alexei Muzarov
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  19. *
  20. * e-mail: azexmail@gmail.com
  21. * e-mail: azexmail@mail.ru
  22. * https://bitbucket.org/azexmail/phpmeccano
  23. */
  24. namespace core;
  25. require_once MECCANO_CORE_DIR.'/files.php';
  26. require_once MECCANO_CORE_DIR.'/langman.php';
  27. require_once MECCANO_CORE_DIR.'/logman.php';
  28. require_once MECCANO_CORE_DIR.'/policy.php';
  29. interface intPlugins {
  30. public function __construct(\mysqli $dbLink);
  31. public function unpack($package);
  32. public function delUnpacked($plugin);
  33. public function listUnpacked();
  34. public function aboutUnpacked($plugin);
  35. public function pluginData($plugin);
  36. public function install($plugin, $reset = false, $log = true);
  37. public function delInstalled($plugin, $keepData = true, $log = true);
  38. public function listInstalled();
  39. public function aboutInstalled($plugin);
  40. }
  41. class Plugins extends ServiceMethods implements intPlugins {
  42. private $langMan;
  43. private $logMan;
  44. private $policyMan;
  45. public function __construct(\mysqli $dbLink) {
  46. $this->dbLink = $dbLink;
  47. $this->langMan = new LangMan($dbLink);
  48. $this->logMan = new LogMan($dbLink);
  49. $this->policyMan = new Policy($dbLink);
  50. }
  51. private function lockPlugins($methodName) {
  52. $this->zeroizeError();
  53. if (is_file(MECCANO_TMP_DIR."/core_plugins_lock")) {
  54. $this->setError(ERROR_RESTRICTED_ACCESS, "$methodName: plugins are locked");
  55. return false;
  56. }
  57. elseif (is_writable(MECCANO_TMP_DIR) && !is_file(MECCANO_TMP_DIR."/core_plugins_lock") && !is_dir(MECCANO_TMP_DIR."/core_plugins_lock")) {
  58. $lock = fopen(MECCANO_TMP_DIR."/core_plugins_lock", 'wb');
  59. fclose($lock);
  60. return true;
  61. }
  62. else {
  63. $this->setError(ERROR_RESTRICTED_ACCESS, "$methodName: unable to lock plugins");
  64. return false;
  65. }
  66. }
  67. public function unpack($package) {
  68. if (!$this->lockPlugins('unpack')) {
  69. return false;
  70. }
  71. if ($this->usePolicy && !$this->checkFuncAccess('core', 'plugins_install')) {
  72. $this->setError(ERROR_RESTRICTED_ACCESS, "unpack: restricted by the policy");
  73. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  74. return false;
  75. }
  76. $zip = new \ZipArchive();
  77. $zipOpen = $zip->open($package);
  78. if ($zipOpen === true) {
  79. $tmpName = makeIdent();
  80. $unpackPath = MECCANO_UNPACKED_PLUGINS."/$tmpName";
  81. $tmpPath = MECCANO_TMP_DIR."/$tmpName";
  82. if (!@$zip->extractTo($tmpPath)) {
  83. $this->setError(ERROR_NOT_EXECUTED, "unpack: unable to extract package to $tmpPath");
  84. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  85. return false;
  86. }
  87. $zip->close();
  88. // validate xml components
  89. $serviceData = new \DOMDocument();
  90. $xmlComponents = [
  91. "languages.xml" => "langman-language-v01.rng",
  92. "policy.xml" => "policy-v01.rng",
  93. "log.xml" => "logman-events-v01.rng",
  94. "texts.xml" => "langman-text-v01.rng",
  95. "titles.xml" => "langman-title-v01.rng",
  96. "depends.xml" => "plugins-package-depends-v01.rng",
  97. "metainfo.xml" => "plugins-package-metainfo-v01.rng"
  98. ];
  99. foreach ($xmlComponents as $valComponent=> $valSchema) {
  100. $xmlComponent = openRead($tmpPath."/$valComponent");
  101. if (!$xmlComponent) {
  102. Files::remove($tmpPath);
  103. $this->setError(ERROR_NOT_EXECUTED, "unpack: unable to read [$valComponent]");
  104. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  105. return false;
  106. }
  107. if (!in_array(mime_content_type($tmpPath."/$valComponent"), ["application/xml", "text/xml"])) {
  108. Files::remove($tmpPath);
  109. $this->setError(ERROR_NOT_EXECUTED, "unpack: [$valComponent] is not XML-structured");
  110. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  111. return false;
  112. }
  113. $serviceData->loadXML($xmlComponent);
  114. if (!@$serviceData->relaxNGValidate(MECCANO_CORE_DIR."/validation-schemas/$valSchema")) {
  115. Files::remove($tmpPath);
  116. $this->setError(ERROR_INCORRECT_DATA, "unpack: invalid [$valComponent] structure");
  117. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  118. return false;
  119. }
  120. }
  121. // get data from metainfo.xml
  122. $packVersion = $serviceData->getElementsByTagName('metainfo')->item(0)->getAttribute('version');
  123. if ($packVersion != '0.3') {
  124. Files::remove($tmpPath);
  125. $this->setError(ERROR_INCORRECT_DATA, "unpack: installer is incompatible with the package specification [$packVersion]");
  126. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  127. return false;
  128. }
  129. $shortName = $serviceData->getElementsByTagName('shortname')->item(0)->nodeValue;
  130. $qIsUnpacked = $this->dbLink->query("SELECT `id` "
  131. . "FROM `".MECCANO_TPREF."_core_plugins_unpacked` "
  132. . "WHERE `short`='$shortName' ;");
  133. if ($this->dbLink->errno) {
  134. Files::remove($tmpPath);
  135. $this->setError(ERROR_NOT_EXECUTED, 'unpack: unable to check whether the plugin is unpacked -> '.$this->dbLink->error);
  136. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  137. return false;
  138. }
  139. if ($this->dbLink->affected_rows) {
  140. Files::remove($tmpPath);
  141. $this->setError(ERROR_ALREADY_EXISTS, "unpack: plugin [$shortName] was already unpacked");
  142. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  143. return false;
  144. }
  145. $fullName = $this->dbLink->real_escape_string($serviceData->getElementsByTagName('fullname')->item(0)->nodeValue);
  146. $version = $serviceData->getElementsByTagName('version')->item(0)->nodeValue;
  147. $insertColumns = "`short`, `full`, `version`, `spec`, `dirname`";
  148. $insertValues = "'$shortName', '$fullName', '$version', '$packVersion', '$tmpName'";
  149. // get optional data
  150. $optionalData = ['about', 'credits', 'url', 'email', 'license'];
  151. foreach ($optionalData as $optNode) {
  152. if ($optional = $serviceData->getElementsByTagName("$optNode")->item(0)->nodeValue) {
  153. $optional = $this->dbLink->real_escape_string($optional);
  154. $insertColumns = $insertColumns.", `$optNode`";
  155. $insertValues = $insertValues.", '$optional'";
  156. }
  157. }
  158. // get list of the needed dependences
  159. $serviceData->load($tmpPath."/depends.xml");
  160. $depends = "";
  161. $dependsNodes = $serviceData->getElementsByTagName("plugin");
  162. foreach ($dependsNodes as $dependsNode) {
  163. $depends = $depends.$dependsNode->getAttribute('name')." (".$dependsNode->getAttribute('operator')." ".$dependsNode->getAttribute('version')."), ";
  164. }
  165. $depends = substr($depends, 0, -2);
  166. $insertColumns = $insertColumns.", `depends`";
  167. $insertValues = $insertValues.", '$depends'";
  168. $this->dbLink->query("INSERT INTO `".MECCANO_TPREF."_core_plugins_unpacked` ($insertColumns)"
  169. . "VALUES ($insertValues) ;");
  170. if ($this->dbLink->errno) {
  171. Files::remove($tmpPath);
  172. $this->setError(ERROR_NOT_EXECUTED, 'unpack: '.$this->dbLink->error);
  173. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  174. return false;
  175. }
  176. if (!Files::move($tmpPath, $unpackPath)) {
  177. $this->setError(Files::errId(), 'unpack: -> '.Files::errExp());
  178. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  179. return false;
  180. }
  181. }
  182. else {
  183. $this->setError(ERROR_NOT_EXECUTED, "unpack: unable to open package. ZipArchive error: $zipOpen");
  184. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  185. return false;
  186. }
  187. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  188. return $shortName;
  189. }
  190. public function delUnpacked($plugin) {
  191. if (!$this->lockPlugins('delUnpacked')) {
  192. return false;
  193. }
  194. if ($this->usePolicy && !$this->checkFuncAccess('core', 'plugins_install')) {
  195. $this->setError(ERROR_RESTRICTED_ACCESS, "delUnpacked: restricted by the policy");
  196. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  197. return false;
  198. }
  199. if (!pregPlugin($plugin)) {
  200. $this->setError(ERROR_INCORRECT_DATA, 'delUnpacked: incorrect plugin name');
  201. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  202. return false;
  203. }
  204. $qUnpacked = $this->dbLink->query("SELECT `dirname` "
  205. . "FROM `".MECCANO_TPREF."_core_plugins_unpacked` "
  206. . "WHERE `short`='$plugin' ;");
  207. if ($this->dbLink->errno) {
  208. $this->setError(ERROR_NOT_EXECUTED, 'delUnpacked: '.$this->dbLink->error);
  209. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  210. return false;
  211. }
  212. if (!$this->dbLink->affected_rows) {
  213. $this->setError(ERROR_NOT_FOUND, "delUnpacked: cannot find defined plugin");
  214. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  215. return false;
  216. }
  217. list($dirName) = $qUnpacked->fetch_row();
  218. if (!Files::remove(MECCANO_UNPACKED_PLUGINS."/$dirName")) {
  219. $this->setError(Files::errId(), 'delUnpacked: -> '.Files::errExp());
  220. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  221. return false;
  222. }
  223. $this->dbLink->query("DELETE FROM `".MECCANO_TPREF."_core_plugins_unpacked` "
  224. . "WHERE `short`='$plugin' ;");
  225. if ($this->dbLink->errno) {
  226. $this->setError(ERROR_NOT_EXECUTED, 'delUnpacked: unable to delete unpacked plugin ->'.$this->dbLink->error);
  227. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  228. return false;
  229. }
  230. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  231. return true;
  232. }
  233. public function listUnpacked() {
  234. $this->zeroizeError();
  235. if ($this->usePolicy && !$this->checkFuncAccess('core', 'plugins_install')) {
  236. $this->setError(ERROR_RESTRICTED_ACCESS, "listUnpacked: restricted by the policy");
  237. return false;
  238. }
  239. $qUncpacked = $this->dbLink->query("SELECT `short`, `full`, `version` "
  240. . "FROM `".MECCANO_TPREF."_core_plugins_unpacked` ;");
  241. if ($this->dbLink->errno) {
  242. $this->setError(ERROR_NOT_EXECUTED, "listUnpacked: ".$this->dbLink->error);
  243. return false;
  244. }
  245. if ($this->outputType == 'xml') {
  246. $xml = new \DOMDocument('1.0', 'utf-8');
  247. $unpackedNode = $xml->createElement('unpacked');
  248. $xml->appendChild($unpackedNode);
  249. while ($row = $qUncpacked->fetch_row()) {
  250. if ($curVersion = $this->pluginData($row[0])) {
  251. $curSumVersion = calcSumVersion($curVersion["version"]);
  252. $newSumVersion = calcSumVersion($row[2]);
  253. if ($curSumVersion < $newSumVersion) {
  254. $action = "upgrade";
  255. }
  256. elseif ($curSumVersion == $newSumVersion) {
  257. $action = "reinstall";
  258. }
  259. elseif ($curSumVersion > $newSumVersion) {
  260. $action = "downgrade";
  261. }
  262. }
  263. else {
  264. $action = "install";
  265. }
  266. $pluginNode = $xml->createElement('plugin');
  267. $unpackedNode->appendChild($pluginNode);
  268. $pluginNode->appendChild($xml->createElement('short', $row[0]));
  269. $pluginNode->appendChild($xml->createElement('full', $row[1]));
  270. $pluginNode->appendChild($xml->createElement('version', $row[2]));
  271. $pluginNode->appendChild($xml->createElement('action', $action));
  272. }
  273. return $xml;
  274. }
  275. else {
  276. $unpacked = [];
  277. while ($row = $qUncpacked->fetch_row()) {
  278. if ($curVersion = $this->pluginData($row[0])) {
  279. $curSumVersion = calcSumVersion($curVersion["version"]);
  280. $newSumVersion = calcSumVersion($row[2]);
  281. if ($curSumVersion < $newSumVersion) {
  282. $action = "upgrade";
  283. }
  284. elseif ($curSumVersion == $newSumVersion) {
  285. $action = "reinstall";
  286. }
  287. elseif ($curSumVersion > $newSumVersion) {
  288. $action = "downgrade";
  289. }
  290. }
  291. else {
  292. $action = "install";
  293. }
  294. $unpacked[] = [
  295. 'short' => $row[0],
  296. 'full' => $row[1],
  297. 'version' => $row[2],
  298. 'action' => $action
  299. ];
  300. }
  301. if ($this->outputType == 'json') {
  302. return json_encode($unpacked);
  303. }
  304. else {
  305. return $unpacked;
  306. }
  307. }
  308. }
  309. public function aboutUnpacked($plugin) {
  310. $this->zeroizeError();
  311. if ($this->usePolicy && !$this->checkFuncAccess('core', 'plugins_install')) {
  312. $this->setError(ERROR_RESTRICTED_ACCESS, "aboutUnpacked: restricted by the policy");
  313. return false;
  314. }
  315. if (!pregPlugin($plugin)) {
  316. $this->setError(ERROR_INCORRECT_DATA, 'aboutUnpacked: incorrect plugin name');
  317. return false;
  318. }
  319. $qUncpacked = $this->dbLink->query("SELECT `short`, `full`, `version`, `about`, `credits`, `url`, `email`, `license`, `depends` "
  320. . "FROM `".MECCANO_TPREF."_core_plugins_unpacked` "
  321. . "WHERE `short`='$plugin' ;");
  322. if ($this->dbLink->errno) {
  323. $this->setError(ERROR_NOT_EXECUTED, "aboutUnpacked: ".$this->dbLink->error);
  324. return false;
  325. }
  326. if (!$this->dbLink->affected_rows) {
  327. $this->setError(ERROR_NOT_FOUND, "aboutUnpacked: plugin not found");
  328. return false;
  329. }
  330. list($shortName, $fullName, $version, $about, $credits, $url, $email, $license, $depends) = $qUncpacked->fetch_row();
  331. if ($curVersion = $this->pluginData($shortName)) {
  332. $curSumVersion = calcSumVersion($curVersion["version"]);
  333. $newSumVersion = calcSumVersion($version);
  334. if ($curSumVersion < $newSumVersion) {
  335. $action = "upgrade";
  336. }
  337. elseif ($curSumVersion == $newSumVersion) {
  338. $action = "reinstall";
  339. }
  340. elseif ($curSumVersion > $newSumVersion) {
  341. $action = "downgrade";
  342. }
  343. }
  344. else {
  345. $action = "install";
  346. }
  347. if ($this->outputType == 'xml') {
  348. $xml = new \DOMDocument('1.0', 'utf-8');
  349. $unpackedNode = $xml->createElement('unpacked');
  350. $xml->appendChild($unpackedNode);
  351. $unpackedNode->appendChild($xml->createElement('short', $shortName));
  352. $unpackedNode->appendChild($xml->createElement('full', $fullName));
  353. $unpackedNode->appendChild($xml->createElement('version', $version));
  354. $unpackedNode->appendChild($xml->createElement('about', $about));
  355. $unpackedNode->appendChild($xml->createElement('credits', $credits));
  356. $unpackedNode->appendChild($xml->createElement('url', $url));
  357. $unpackedNode->appendChild($xml->createElement('email', $email));
  358. $unpackedNode->appendChild($xml->createElement('license', $license));
  359. $unpackedNode->appendChild($xml->createElement('depends', $depends));
  360. $unpackedNode->appendChild($xml->createElement('action', $action));
  361. return $xml;
  362. }
  363. else {
  364. $unpacked = [
  365. 'short' => $shortName,
  366. 'full' => $fullName,
  367. 'version' => $version,
  368. 'about' => $about,
  369. 'credits' => $credits,
  370. 'url' => $url,
  371. 'email' => $email,
  372. 'license' => $license,
  373. 'depends' => $depends,
  374. 'action' => $action
  375. ];
  376. if ($this->outputType == 'json'){
  377. return json_encode($unpacked);
  378. }
  379. else {
  380. return $unpacked;
  381. }
  382. }
  383. }
  384. public function pluginData($plugin) {
  385. $this->zeroizeError();
  386. if (!pregPlugin($plugin)) {
  387. $this->setError(ERROR_INCORRECT_DATA, "pluginData: incorrect name");
  388. return false;
  389. }
  390. $qPlugin = $this->dbLink->query("SELECT `id`, `version` "
  391. . "FROM `".MECCANO_TPREF."_core_plugins_installed` "
  392. . "WHERE `name`='$plugin'");
  393. if ($this->dbLink->errno) {
  394. $this->setError(ERROR_NOT_EXECUTED, "pluginData: unable to get plugin version -> ".$this->dbLink->error);
  395. return false;
  396. }
  397. if (!$this->dbLink->affected_rows) {
  398. return false;
  399. }
  400. list($id, $version) = $qPlugin->fetch_row();
  401. return ["id" => (int) $id, "version" => $version];
  402. }
  403. public function install($plugin, $reset = false, $log = true) {
  404. if (!$this->lockPlugins('install')) {
  405. return false;
  406. }
  407. if ($this->usePolicy && !$this->checkFuncAccess('core', 'plugins_install')) {
  408. $this->setError(ERROR_RESTRICTED_ACCESS, "install: restricted by the policy");
  409. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  410. return false;
  411. }
  412. if (!pregPlugin($plugin) || !is_bool($reset)) {
  413. $this->setError(ERROR_INCORRECT_DATA, "install: incorrect argument(s)");
  414. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  415. return false;
  416. }
  417. $qPlugin = $this->dbLink->query("SELECT `short`, `full`, `version`, `spec`, `dirname`, `about`, `credits`, `url`, `email`, `license` "
  418. . "FROM `".MECCANO_TPREF."_core_plugins_unpacked` "
  419. . "WHERE `short`='$plugin' ;");
  420. if ($this->dbLink->errno) {
  421. $this->setError(ERROR_NOT_EXECUTED, "install: ".$this->dbLink->error);
  422. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  423. return false;
  424. }
  425. if (!$this->dbLink->affected_rows) {
  426. $this->setError(ERROR_NOT_FOUND, "install: unpacked plugin [$plugin] not found");
  427. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  428. return false;
  429. }
  430. list($shortName, $fullName, $version, $packVersion, $plugDir, $about, $credits, $url, $email, $license) = $qPlugin->fetch_row();
  431. if ($packVersion != '0.3') {
  432. $this->setError(ERROR_INCORRECT_DATA, "install: installer is incompatible with the package specification [$packVersion]");
  433. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  434. return false;
  435. }
  436. // revalidate xml components
  437. $plugPath = MECCANO_UNPACKED_PLUGINS."/$plugDir";
  438. $serviceData = new \DOMDocument();
  439. $xmlComponents = [
  440. "languages.xml" => "langman-language-v01.rng",
  441. "policy.xml" => "policy-v01.rng",
  442. "log.xml" => "logman-events-v01.rng",
  443. "texts.xml" => "langman-text-v01.rng",
  444. "titles.xml" => "langman-title-v01.rng",
  445. "depends.xml" => "plugins-package-depends-v01.rng"
  446. ];
  447. foreach ($xmlComponents as $valComponent=> $valSchema) {
  448. $xmlComponent = openRead($plugPath."/$valComponent");
  449. if (!$xmlComponent) {
  450. $this->setError(ERROR_NOT_EXECUTED, "unpack: unable to read [$valComponent]");
  451. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  452. return false;
  453. }
  454. if (!in_array(mime_content_type($plugPath."/$valComponent"), ["application/xml", "text/xml"])) {
  455. $this->setError(ERROR_NOT_EXECUTED, "unpack: [$valComponent] is not XML-structured");
  456. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  457. return false;
  458. }
  459. $serviceData->loadXML($xmlComponent);
  460. if (!@$serviceData->relaxNGValidate(MECCANO_CORE_DIR."/validation-schemas/$valSchema")) {
  461. $this->setError(ERROR_INCORRECT_DATA, "unpack: invalid [$valComponent] structure");
  462. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  463. return false;
  464. }
  465. }
  466. // check for plugin dependences
  467. $dependsNodes = $serviceData->getElementsByTagName("plugin");
  468. foreach ($dependsNodes as $dependsNode) {
  469. $depPlugin = $dependsNode->getAttribute('name');
  470. $depVersion = $dependsNode->getAttribute('version');
  471. $operator = $dependsNode->getAttribute('operator');
  472. $existDep = $this->pluginData($depPlugin);
  473. if (!$existDep || !compareVersions($existDep["version"], $depVersion, $operator)) {
  474. $this->setError(ERROR_NOT_FOUND, "install: required $depPlugin ($operator $depVersion)");
  475. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  476. return false;
  477. }
  478. }
  479. // check existence of the required files and directories
  480. $requiredFiles = ["inst.php", "rm.php"];
  481. foreach ($requiredFiles as $fileName) {
  482. if (!is_file($plugPath."/$fileName")) {
  483. $this->setError(ERROR_NOT_FOUND, "install: file [$fileName] is required");
  484. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  485. return false;
  486. }
  487. }
  488. $requiredDirs = [$plugPath."/documents", $plugPath."/js", $plugPath."/css", $plugPath."/php", MECCANO_DOCUMENTS_DIR, MECCANO_JS_DIR, MECCANO_PHP_DIR];
  489. foreach ($requiredDirs as $dirName) {
  490. if (!is_dir($dirName)) {
  491. $this->setError(ERROR_NOT_FOUND, "install: directory [$dirName] is required");
  492. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  493. return false;
  494. }
  495. }
  496. // get identifier and version of the being installed plugin
  497. if ($idAndVersion = $this->pluginData($shortName)) {
  498. $existId = (int) $idAndVersion["id"]; // identifier of the being reinstalled/upgraded/downgraded plugin
  499. $existVersion = $idAndVersion["version"]; // version of the being reinstalled/upgraded/downgraded plugin
  500. }
  501. else {
  502. $this->dbLink->query(
  503. "INSERT INTO `".MECCANO_TPREF."_core_plugins_installed` "
  504. . "(`name`, `version`) "
  505. . "VALUES ('$shortName', '$version') ;"
  506. );
  507. if ($this->dbLink->errno) {
  508. $this->setError(ERROR_NOT_EXECUTED, "install: ".$this->dbLink->error);
  509. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  510. return false;
  511. }
  512. $existId = (int) $this->dbLink->insert_id; // identifier if the being installed plugin
  513. $existVersion = ""; // empty version of the being installed plugin
  514. }
  515. // insert or update information about plugin
  516. if ($existVersion) {
  517. $sql = [
  518. "UPDATE `".MECCANO_TPREF."_core_plugins_installed` "
  519. . "SET `version`='$version' "
  520. . "WHERE `id`=$existId ;",
  521. "UPDATE `".MECCANO_TPREF."_core_plugins_installed_about` "
  522. . "SET `full`='$fullName', "
  523. . "`about`='$about', "
  524. . "`credits`='$credits', "
  525. . "`url`='$url', "
  526. . "`email`='$email', "
  527. . "`license`='$license' "
  528. . "WHERE `id`=$existId"
  529. ];
  530. }
  531. else {
  532. $sql = [
  533. "INSERT INTO `".MECCANO_TPREF."_core_plugins_installed_about` "
  534. . "(`id`, `full`, `about`, `credits`, `url`, `email`, `license`) "
  535. . "VALUES ($existId, '$fullName', '$about', '$credits', '$url', '$email', '$license') ;"
  536. ];
  537. }
  538. foreach ($sql as $value) {
  539. $this->dbLink->query($value);
  540. if ($this->dbLink->errno) {
  541. $this->setError(ERROR_NOT_EXECUTED, "install: ".$this->dbLink->error);
  542. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  543. return false;
  544. }
  545. }
  546. // run preinstallation
  547. require_once $plugPath.'/inst.php';
  548. $instObject = new Install($this->dbLink, $existId, $existVersion, $reset);
  549. if (!$instObject->preinst()) {
  550. $this->setError($instObject->errId(), "install -> ".$instObject->errExp());
  551. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  552. return false;
  553. }
  554. // install languages
  555. $serviceData->load($plugPath.'/languages.xml');
  556. if (!$this->langMan->installLang($serviceData, false)) {
  557. $this->setError($this->errId(), "install -> ".$this->errExp());
  558. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  559. return false;
  560. }
  561. // install policy access rules
  562. $serviceData->load($plugPath.'/policy.xml');
  563. if (!$this->policyMan->installPolicy($serviceData, false)) {
  564. $this->setError($this->errId(), "install -> ".$this->errExp());
  565. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  566. return false;
  567. }
  568. // install log events
  569. $serviceData->load($plugPath.'/log.xml');
  570. if (!$this->logMan->installEvents($serviceData, false)) {
  571. $this->setError($this->errId(), "install -> ".$this->errExp());
  572. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  573. return false;
  574. }
  575. // install texts
  576. $serviceData->load($plugPath.'/texts.xml');
  577. if (!$this->langMan->installTexts($serviceData, false)) {
  578. $this->setError($this->errId(), "install -> ".$this->errExp());
  579. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  580. return false;
  581. }
  582. // install titles
  583. $serviceData->load($plugPath.'/titles.xml');
  584. if (!$this->langMan->installTitles($serviceData, false)) {
  585. $this->setError($this->errId(), "install -> ".$this->errExp());
  586. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  587. return false;
  588. }
  589. // copy files and directories to their destinations
  590. if ($shortName == "core") {
  591. $docDest = MECCANO_CORE_DIR;
  592. }
  593. else {
  594. $docDest = MECCANO_PHP_DIR."/$shortName";
  595. }
  596. $beingCopied = [
  597. "documents" => MECCANO_DOCUMENTS_DIR."/$shortName",
  598. "php" => $docDest,
  599. "js" => MECCANO_JS_DIR."/$shortName",
  600. "css" => MECCANO_CSS_DIR."/$shortName",
  601. "rm.php" => MECCANO_UNINSTALL."/$shortName.php"
  602. ];
  603. foreach ($beingCopied as $source => $dest) {
  604. if (!Files::copy($plugPath."/$source", $dest, true, true, false, false, false, false, true)) {
  605. $this->setError(Files::errId(), "install -> ".Files::errExp());
  606. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  607. return false;
  608. }
  609. }
  610. // run postinstallation
  611. if (!$instObject->postinst()) {
  612. $this->setError($instObject->errId(), "install -> ".$instObject->errExp());
  613. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  614. return false;
  615. }
  616. //
  617. if ($log && !$this->newLogRecord('core', 'plugins_install', "$shortName; v$version; ID: $existId")) {
  618. $this->setError(ERROR_NOT_CRITICAL, "install -> ".$this->errExp());
  619. }
  620. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  621. return true;
  622. }
  623. public function delInstalled($plugin, $keepData = true, $log = true) {
  624. if (!$this->lockPlugins('delInstalled')) {
  625. return false;
  626. }
  627. if ($this->usePolicy && !$this->checkFuncAccess('core', 'plugins_del_installed')) {
  628. $this->setError(ERROR_RESTRICTED_ACCESS, "delInstalled: restricted by the policy");
  629. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  630. return false;
  631. }
  632. if (!pregPlugin($plugin) || !is_bool($keepData)) {
  633. $this->setError(ERROR_INCORRECT_DATA, "delInstalled: incorrect argument(s)");
  634. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  635. return false;
  636. }
  637. // check whether the plugin installed
  638. $qPlugin = $this->dbLink->query("SELECT `id`, `name`, `version` "
  639. . "FROM `".MECCANO_TPREF."_core_plugins_installed` "
  640. . "WHERE `name`='$plugin' ;");
  641. if (!$this->dbLink->affected_rows) {
  642. $this->setError(ERROR_NOT_FOUND, "delInstalled: plugin not found");
  643. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  644. return false;
  645. }
  646. if ($this->dbLink->errno) {
  647. $this->setError(ERROR_NOT_EXECUTED, "delInstalled: ".$this->dbLink->error);
  648. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  649. return false;
  650. }
  651. list($id, $shortName, $version) = $qPlugin->fetch_row();
  652. if (strtolower($shortName) == "core") {
  653. $this->setError(ERROR_SYSTEM_INTERVENTION, "delInstalled: unable to remove [core]");
  654. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  655. return false;
  656. }
  657. // check whether the removement script exists
  658. if (!is_file(MECCANO_UNINSTALL."/$shortName.php")) {
  659. $this->setError(ERROR_NOT_FOUND, "delInstalled: removement script [".MECCANO_UNINSTALL."/$shortName.php] not found");
  660. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  661. return false;
  662. }
  663. //run preremovement
  664. require_once MECCANO_UNINSTALL."/$shortName.php";
  665. $rmObject = new Remove($this->dbLink, $id, $keepData);
  666. if (!$rmObject->prerm()) {
  667. $this->setError($rmObject->errId(), "delInstalled -> ".$rmObject->errExp());
  668. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  669. return false;
  670. }
  671. // remove policy access rules
  672. if (!$this->policyMan->delPolicy($shortName)) {
  673. $this->setError($this->errId(), "delInstalled -> ".$this->errExp());
  674. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  675. return false;
  676. }
  677. // remove log events
  678. if (!$this->logMan->delLogEvents($shortName)) {
  679. $this->setError($this->errId(), "delInstalled -> ".$this->errExp());
  680. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  681. return false;
  682. }
  683. // remove texts and titles
  684. if (!$this->langMan->delPlugin($shortName)) {
  685. $this->setError($this->errId(), "delInstalled -> ".$this->errExp());
  686. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  687. return false;
  688. }
  689. // run postremovement
  690. if (!$rmObject->postrm()) {
  691. $this->setError($rmObject->errId(), "delInstalled -> ".$rmObject->errExp());
  692. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  693. return false;
  694. }
  695. // remove files and directories of the plugin
  696. $beingRemoved = [
  697. "php" => MECCANO_PHP_DIR."/$shortName",
  698. "js" => MECCANO_JS_DIR."/$shortName",
  699. "css" => MECCANO_CSS_DIR."/$shortName",
  700. "rm.php" => MECCANO_UNINSTALL."/$shortName.php"
  701. ];
  702. if (!$keepData) {
  703. $beingRemoved["documents"] = MECCANO_DOCUMENTS_DIR."/$shortName";
  704. }
  705. foreach ($beingRemoved as $source) {
  706. if (!Files::remove($source)) {
  707. $this->setError(Files::errId(), "delInstalled -> ".Files::errExp());
  708. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  709. return false;
  710. }
  711. }
  712. // delete information about plugin
  713. $sql = [
  714. "DELETE FROM `".MECCANO_TPREF."_core_plugins_installed_about` "
  715. . "WHERE `id`=$id",
  716. "DELETE FROM `".MECCANO_TPREF."_core_plugins_installed` "
  717. . "WHERE `id`=$id",
  718. ];
  719. foreach ($sql as $value) {
  720. $this->dbLink->query($value);
  721. if ($this->dbLink->errno) {
  722. $this->setError(ERROR_NOT_EXECUTED, "delInstalled: ".$this->dbLink->error);
  723. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  724. return false;
  725. }
  726. }
  727. //
  728. if ($log && !$this->newLogRecord('core', 'plugins_del_installed', "$shortName; v$version; ID: $id")) {
  729. $this->setError(ERROR_NOT_CRITICAL, "install -> ".$this->errExp());
  730. }
  731. unlink(MECCANO_TMP_DIR."/core_plugins_lock");
  732. return true;
  733. }
  734. public function listInstalled() {
  735. $this->zeroizeError();
  736. if ($this->usePolicy && !$this->checkFuncAccess('core', 'plugins_installed')) {
  737. $this->setError(ERROR_RESTRICTED_ACCESS, "listInstalled: restricted by the policy");
  738. return false;
  739. }
  740. $qInstalled = $this->dbLink->query("SELECT `i`.`name`, `a`.`full`, `i`.`version`, `i`.`time` "
  741. . "FROM `".MECCANO_TPREF."_core_plugins_installed` `i` "
  742. . "JOIN `".MECCANO_TPREF."_core_plugins_installed_about` `a` "
  743. . "ON `a`.`id`=`i`.`id` ;");
  744. if ($this->dbLink->errno) {
  745. $this->setError(ERROR_NOT_EXECUTED, "listInstalled: ".$this->dbLink->error);
  746. return false;
  747. }
  748. if ($this->outputType == 'xml') {
  749. $xml = new \DOMDocument('1.0', 'utf-8');
  750. $installedNode = $xml->createElement("installed");
  751. $xml->appendChild($installedNode);
  752. while ($row = $qInstalled->fetch_row()) {
  753. $pluginNode = $xml->createElement("plugin");
  754. $installedNode->appendChild($pluginNode);
  755. $pluginNode->appendChild($xml->createElement("short", $row[0]));
  756. $pluginNode->appendChild($xml->createElement("full", $row[1]));
  757. $pluginNode->appendChild($xml->createElement("version", $row[2]));
  758. $pluginNode->appendChild($xml->createElement("time", $row[3]));
  759. }
  760. return $xml;
  761. }
  762. else {
  763. $installed = [];
  764. while ($row = $qInstalled->fetch_row()) {
  765. $installed[] = [
  766. "short" => $row[0],
  767. "full" => $row[1],
  768. "version" => $row[2],
  769. "time" => $row[3]
  770. ];
  771. }
  772. if ($this->outputType == 'json') {
  773. return json_encode($installed);
  774. }
  775. else {
  776. return $installed;
  777. }
  778. }
  779. }
  780. public function aboutInstalled($plugin) {
  781. $this->zeroizeError();
  782. if ($this->usePolicy && !$this->checkFuncAccess('core', 'plugins_installed')) {
  783. $this->setError(ERROR_RESTRICTED_ACCESS, "aboutInstalled: restricted by the policy");
  784. return false;
  785. }
  786. if (!pregPlugin($plugin)) {
  787. $this->setError(ERROR_INCORRECT_DATA, "aboutInstalled: incorrect plugin name");
  788. return false;
  789. }
  790. $qPlugin = $this->dbLink->query("SELECT `i`.`name`, `a`.`full`, `i`.`version`, `i`.`time`, `a`.`about`, `a`.`credits`, `a`.`url`, `a`.`email`, `a`.`license` "
  791. . "FROM `".MECCANO_TPREF."_core_plugins_installed` `i` "
  792. . "JOIN `".MECCANO_TPREF."_core_plugins_installed_about` `a` "
  793. . "ON `a`.`id`=`i`.`id` "
  794. . "WHERE `i`.`name`='$plugin' ;");
  795. if ($this->dbLink->errno) {
  796. $this->setError(ERROR_NOT_EXECUTED, "aboutInstalled: ".$this->dbLink->error);
  797. return false;
  798. }
  799. if (!$this->dbLink->affected_rows) {
  800. $this->setError(ERROR_NOT_FOUND, "aboutInstalled: plugin not found");
  801. return false;
  802. }
  803. list($shortName, $fullName, $version, $instTime, $about, $credits, $url, $email, $license) = $qPlugin->fetch_row();
  804. if ($this->outputType == 'xml') {
  805. $xml = new \DOMDocument('1.0', 'utf-8');
  806. $installedNode = $xml->createElement("installed");
  807. $xml->appendChild($installedNode);
  808. $installedNode->appendChild($xml->createElement("short", $shortName));
  809. $installedNode->appendChild($xml->createElement("full", $fullName));
  810. $installedNode->appendChild($xml->createElement("version", $version));
  811. $installedNode->appendChild($xml->createElement("time", $instTime));
  812. $installedNode->appendChild($xml->createElement("about", $about));
  813. $installedNode->appendChild($xml->createElement("credits", $credits));
  814. $installedNode->appendChild($xml->createElement("url", $url));
  815. $installedNode->appendChild($xml->createElement("email", $email));
  816. $installedNode->appendChild($xml->createElement("license", $license));
  817. return $xml;
  818. }
  819. else {
  820. $installed = [
  821. "short" => $shortName,
  822. "full" => $fullName,
  823. "version" => $version,
  824. "time" => $instTime,
  825. "about" =>$about,
  826. "credits" => $credits,
  827. "url" => $url,
  828. "email" => $email,
  829. "license" => $license
  830. ];
  831. if ($this->outputType == 'json') {
  832. return json_encode($installed);
  833. }
  834. else {
  835. return $installed;
  836. }
  837. }
  838. }
  839. }