core-webinterface.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. <?php
  2. // Copyright (c) 2013-2014 Datenstrom, http://datenstrom.se
  3. // This file may be used and distributed under the terms of the public license.
  4. // Web interface core plugin
  5. class YellowWebinterface
  6. {
  7. const Version = "0.4.9";
  8. var $yellow; //access to API
  9. var $active; //web interface is active? (boolean)
  10. var $userLoginFailed; //web interface login failed? (boolean)
  11. var $userPermission; //web interface can modify page? (boolean)
  12. var $users; //web interface users
  13. var $merge; //web interface merge
  14. var $rawDataSource; //raw data of page for comparison
  15. var $rawDataEdit; //raw data of page for editing
  16. // Handle plugin initialisation
  17. function onLoad($yellow)
  18. {
  19. $this->yellow = $yellow;
  20. $this->users = new YellowWebinterfaceUsers($yellow);
  21. $this->merge = new YellowWebinterfaceMerge($yellow);
  22. $this->yellow->config->setDefault("webinterfaceLocation", "/edit/");
  23. $this->yellow->config->setDefault("webinterfaceServerScheme", "http");
  24. $this->yellow->config->setDefault("webinterfaceServerName", $this->yellow->config->get("serverName"));
  25. $this->yellow->config->setDefault("webinterfaceUserHashAlgorithm", "bcrypt");
  26. $this->yellow->config->setDefault("webinterfaceUserHashCost", "10");
  27. $this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
  28. $this->yellow->config->setDefault("webinterfaceFilePrefix", "published");
  29. $this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile"));
  30. }
  31. // Handle request
  32. function onRequest($serverScheme, $serverName, $base, $location, $fileName)
  33. {
  34. $statusCode = 0;
  35. if($this->checkRequest($location))
  36. {
  37. list($serverScheme, $serverName, $base, $location, $fileName) = $this->updateRequestInformation();
  38. $statusCode = $this->processRequest($serverScheme, $serverName, $base, $location, $fileName);
  39. } else {
  40. $activeLocation = $this->yellow->config->get("webinterfaceLocation");
  41. if(rtrim($location, '/') == rtrim($activeLocation, '/'))
  42. {
  43. $statusCode = 301;
  44. $locationHeader = $this->yellow->toolbox->getLocationHeader(
  45. $this->yellow->config->get("webinterfaceServerScheme"),
  46. $this->yellow->config->get("webinterfaceServerName"), $base, $activeLocation);
  47. $this->yellow->sendStatus($statusCode, $locationHeader, false);
  48. }
  49. }
  50. return $statusCode;
  51. }
  52. // Handle page meta data parsing
  53. function onParseMeta($page, $text)
  54. {
  55. if($this->isActive() && $this->isUser())
  56. {
  57. if($page == $this->yellow->page)
  58. {
  59. if(empty($this->rawDataSource)) $this->rawDataSource = $page->rawData;
  60. if(empty($this->rawDataEdit)) $this->rawDataEdit = $page->rawData;
  61. if($page->statusCode == 424)
  62. {
  63. $title = $this->yellow->toolbox->createTextTitle($page->location);
  64. $this->rawDataEdit = $this->getDataNew($title);
  65. }
  66. }
  67. }
  68. }
  69. // Handle page extra header
  70. function onHeaderExtra($page)
  71. {
  72. $header = "";
  73. if($this->isActive())
  74. {
  75. $location = $this->yellow->config->getHtml("serverBase").$this->yellow->config->getHtml("pluginLocation");
  76. $header .= "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"{$location}core-webinterface.css\" />\n";
  77. $header .= "<script type=\"text/javascript\" src=\"{$location}core-webinterface.js\"></script>\n";
  78. $header .= "<script type=\"text/javascript\">\n";
  79. $header .= "// <![CDATA[\n";
  80. if($this->isUser())
  81. {
  82. $header .= "yellow.page.userPermission = ".json_encode($this->userPermission).";\n";
  83. $header .= "yellow.page.rawDataSource = ".json_encode($this->rawDataSource).";\n";
  84. $header .= "yellow.page.rawDataEdit = ".json_encode($this->rawDataEdit).";\n";
  85. $header .= "yellow.page.rawDataNew = ".json_encode($this->getDataNew()).";\n";
  86. $header .= "yellow.page.parserSafeMode = ".json_encode($page->parserSafeMode).";\n";
  87. $header .= "yellow.page.statusCode = ".json_encode($page->statusCode).";\n";
  88. }
  89. $header .= "yellow.config = ".json_encode($this->getDataConfig()).";\n";
  90. $language = $this->isUser() ? $this->users->getLanguage() : $page->get("language");
  91. if(!$this->yellow->text->isLanguage($language)) $language = $this->yellow->config->get("language");
  92. $header .= "yellow.text = ".json_encode($this->yellow->text->getData("webinterface", $language)).";\n";
  93. if(defined("DEBUG")) $header .= "yellow.debug = ".json_encode(DEBUG).";\n";
  94. $header .= "// ]]>\n";
  95. $header .= "</script>\n";
  96. }
  97. return $header;
  98. }
  99. // Handle command help
  100. function onCommandHelp()
  101. {
  102. return "user EMAIL PASSWORD [NAME LANGUAGE HOME]\n";
  103. }
  104. // Handle command
  105. function onCommand($args)
  106. {
  107. list($name, $command) = $args;
  108. switch($command)
  109. {
  110. case "user": $statusCode = $this->userCommand($args); break;
  111. default: $statusCode = 0;
  112. }
  113. return $statusCode;
  114. }
  115. // Create or update user account
  116. function userCommand($args)
  117. {
  118. $statusCode = 0;
  119. list($dummy, $command, $email, $password, $name, $language, $home) = $args;
  120. if(!empty($email) && !empty($password) && (empty($home) || $home[0]=='/'))
  121. {
  122. $fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile");
  123. $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
  124. $cost = $this->yellow->config->get("webinterfaceUserHashCost");
  125. $hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost);
  126. if(empty($hash))
  127. {
  128. $statusCode = 500;
  129. echo "ERROR creating hash: Algorithm '$algorithm' not supported!\n";
  130. } else {
  131. $statusCode = $this->users->createUser($fileName, $email, $hash, $name, $language, $home) ? 200 : 500;
  132. if($statusCode != 200) echo "ERROR updating configuration: Can't write file '$fileName'!\n";
  133. }
  134. echo "Yellow $command: User account ".($statusCode!=200 ? "not " : "");
  135. echo ($this->users->isExisting($email) ? "updated" : "created")."\n";
  136. } else {
  137. echo "Yellow $command: Invalid arguments\n";
  138. $statusCode = 400;
  139. }
  140. return $statusCode;
  141. }
  142. // Process request
  143. function processRequest($serverScheme, $serverName, $base, $location, $fileName)
  144. {
  145. $statusCode = 0;
  146. if($this->checkUser($location, $fileName))
  147. {
  148. switch($_POST["action"])
  149. {
  150. case "": $statusCode = $this->processRequestShow($serverScheme, $serverName, $base, $location, $fileName); break;
  151. case "create": $statusCode = $this->processRequestCreate($serverScheme, $serverName, $base, $location, $fileName); break;
  152. case "edit": $statusCode = $this->processRequestEdit($serverScheme, $serverName, $base, $location, $fileName); break;
  153. case "delete": $statusCode = $this->processRequestDelete($serverScheme, $serverName, $base, $location, $fileName); break;
  154. case "login": $statusCode = $this->processRequestLogin($serverScheme, $serverName, $base, $location, $fileName); break;
  155. case "logout": $statusCode = $this->processRequestLogout($serverScheme, $serverName, $base, $location, $fileName); break;
  156. }
  157. }
  158. if($statusCode == 0)
  159. {
  160. $statusCode = $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
  161. if($this->userLoginFailed) $this->yellow->page->error(401);
  162. }
  163. return $statusCode;
  164. }
  165. // Process request to show page
  166. function processRequestShow($serverScheme, $serverName, $base, $location, $fileName)
  167. {
  168. $statusCode = 0;
  169. if(is_readable($fileName))
  170. {
  171. $statusCode = $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
  172. } else {
  173. if($this->yellow->toolbox->isFileLocation($location) && $this->yellow->isContentDirectory("$location/"))
  174. {
  175. $statusCode = 301;
  176. $locationHeader = $this->yellow->toolbox->getLocationHeader($serverScheme, $serverName, $base, "$location/");
  177. $this->yellow->sendStatus($statusCode, $locationHeader, false);
  178. } else {
  179. $statusCode = $this->userPermission ? 424 : 404;
  180. $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
  181. $this->yellow->page->error($statusCode);
  182. }
  183. }
  184. return $statusCode;
  185. }
  186. // Process request to create page
  187. function processRequestCreate($serverScheme, $serverName, $base, $location, $fileName)
  188. {
  189. $statusCode = 0;
  190. if($this->userPermission && !empty($_POST["rawdataedit"]))
  191. {
  192. $this->rawDataSource = $this->rawDataEdit = rawurldecode($_POST["rawdatasource"]);
  193. $page = $this->getPageNew($serverScheme, $serverName, $base, $location, $fileName, rawurldecode($_POST["rawdataedit"]));
  194. if(!$page->isError())
  195. {
  196. if($this->yellow->toolbox->createFile($page->fileName, $page->rawData))
  197. {
  198. $statusCode = 303;
  199. $locationHeader = $this->yellow->toolbox->getLocationHeader($serverScheme, $serverName, $base, $page->location);
  200. $this->yellow->sendStatus($statusCode, $locationHeader, false);
  201. } else {
  202. $statusCode = 500;
  203. $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
  204. $this->yellow->page->error($statusCode, "Can't write file '$page->fileName'!");
  205. }
  206. } else {
  207. $statusCode = 500;
  208. $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, $false);
  209. $this->yellow->page->error($statusCode, $page->get("pageError"));
  210. }
  211. }
  212. return $statusCode;
  213. }
  214. // Process request to edit page
  215. function processRequestEdit($serverScheme, $serverName, $base, $location, $fileName)
  216. {
  217. $statusCode = 0;
  218. if($this->userPermission && !empty($_POST["rawdataedit"]))
  219. {
  220. $this->rawDataSource = rawurldecode($_POST["rawdatasource"]);
  221. $this->rawDataEdit = rawurldecode($_POST["rawdataedit"]);
  222. $page = $this->getPageUpdate($serverScheme, $serverName, $base, $location, $fileName,
  223. $this->rawDataSource, $this->rawDataEdit, $this->yellow->toolbox->getFileData($fileName));
  224. if(!$page->isError())
  225. {
  226. if($this->yellow->toolbox->renameFile($fileName, $page->fileName) &&
  227. $this->yellow->toolbox->createFile($page->fileName, $page->rawData))
  228. {
  229. $statusCode = 303;
  230. $locationHeader = $this->yellow->toolbox->getLocationHeader($serverScheme, $serverName, $base, $page->location);
  231. $this->yellow->sendStatus($statusCode, $locationHeader, false);
  232. } else {
  233. $statusCode = 500;
  234. $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
  235. $this->yellow->page->error($statusCode, "Can't write file '$page->fileName'!");
  236. }
  237. } else {
  238. $statusCode = 500;
  239. $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
  240. $this->yellow->page->error($statusCode, $page->get("pageError"));
  241. }
  242. }
  243. return $statusCode;
  244. }
  245. // Process request to delete page
  246. function processRequestDelete($serverScheme, $serverName, $base, $location, $fileName)
  247. {
  248. $statusCode = 0;
  249. if($this->userPermission)
  250. {
  251. $this->rawDataSource = $this->rawDataEdit = rawurldecode($_POST["rawdatasource"]);
  252. if(!is_file($fileName) || $this->yellow->toolbox->deleteFile($fileName))
  253. {
  254. $statusCode = 303;
  255. $locationHeader = $this->yellow->toolbox->getLocationHeader($serverScheme, $serverName, $base, $location);
  256. $this->yellow->sendStatus($statusCode, $locationHeader, false);
  257. } else {
  258. $statusCode = 500;
  259. $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
  260. $this->yellow->page->error($statusCode, "Can't delete file '$fileName'!");
  261. }
  262. }
  263. return $statusCode;
  264. }
  265. // Process request for user login
  266. function processRequestLogin($serverScheme, $serverName, $base, $location, $fileName)
  267. {
  268. $statusCode = 0;
  269. $home = $this->users->getHome();
  270. if(substru($location, 0, strlenu($home)) == $home)
  271. {
  272. $statusCode = 303;
  273. $locationHeader = $this->yellow->toolbox->getLocationHeader($serverScheme, $serverName, $base, $location);
  274. $this->yellow->sendStatus($statusCode, $locationHeader, false);
  275. } else {
  276. $statusCode = 302;
  277. $locationHeader = $this->yellow->toolbox->getLocationHeader($serverScheme, $serverName, $base, $home);
  278. $this->yellow->sendStatus($statusCode, $locationHeader, false);
  279. }
  280. return $statusCode;
  281. }
  282. // Process request for user logout
  283. function processRequestLogout($serverScheme, $serverName, $base, $location, $fileName)
  284. {
  285. $statusCode = 302;
  286. $this->users->destroyCookie("login");
  287. $this->users->email = "";
  288. $locationHeader = $this->yellow->toolbox->getLocationHeader(
  289. $this->yellow->config->get("serverScheme"),
  290. $this->yellow->config->get("serverName"),
  291. $this->yellow->config->get("serverBase"), $location);
  292. $this->yellow->sendStatus($statusCode, $locationHeader, false);
  293. return $statusCode;
  294. }
  295. // Check web interface request
  296. function checkRequest($location)
  297. {
  298. if($this->yellow->toolbox->getServerScheme()==$this->yellow->config->get("webinterfaceServerScheme") &&
  299. $this->yellow->toolbox->getServerName()==$this->yellow->config->get("webinterfaceServerName"))
  300. {
  301. $locationLength = strlenu($this->yellow->config->get("webinterfaceLocation"));
  302. $this->active = substru($location, 0, $locationLength) == $this->yellow->config->get("webinterfaceLocation");
  303. }
  304. return $this->isActive();
  305. }
  306. // Check web interface user
  307. function checkUser($location, $fileName)
  308. {
  309. if($_POST["action"] == "login")
  310. {
  311. $email = $_POST["email"];
  312. $password = $_POST["password"];
  313. if($this->users->checkUser($email, $password))
  314. {
  315. $this->users->createCookie("login", $email);
  316. $this->users->email = $email;
  317. $this->userPermission = $this->getUserPermission($location, $fileName);
  318. } else {
  319. $this->userLoginFailed = true;
  320. }
  321. } else if(isset($_COOKIE["login"])) {
  322. list($email, $session) = $this->users->getCookieInformation($_COOKIE["login"]);
  323. if($this->users->checkCookie($email, $session))
  324. {
  325. $this->users->email = $email;
  326. $this->userPermission = $this->getUserPermission($location, $fileName);
  327. } else {
  328. $this->userLoginFailed = true;
  329. }
  330. }
  331. return $this->isUser();
  332. }
  333. // Return permission to modify page
  334. function getUserPermission($location, $fileName)
  335. {
  336. $userPermission = true;
  337. foreach($this->yellow->plugins->plugins as $key=>$value)
  338. {
  339. if(method_exists($value["obj"], "onUserPermission"))
  340. {
  341. $userPermission = $value["obj"]->onUserPermission($location, $fileName, $this->users);
  342. if(!$userPermission) break;
  343. }
  344. }
  345. $userPermission &= is_dir(dirname($fileName)) && strlenu(basename($fileName))<128;
  346. return $userPermission;
  347. }
  348. // Update request information
  349. function updateRequestInformation()
  350. {
  351. $serverScheme = $this->yellow->config->get("webinterfaceServerScheme");
  352. $serverName = $this->yellow->config->get("webinterfaceServerName");
  353. $base = rtrim($this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation"), '/');
  354. $this->yellow->page->base = $base;
  355. return $this->yellow->getRequestInformation($serverScheme, $serverName, $base);
  356. }
  357. // Update page data with title
  358. function updateDataTitle($rawData, $title)
  359. {
  360. foreach($this->yellow->toolbox->getTextLines($rawData) as $line)
  361. {
  362. if(preg_match("/^(\s*Title\s*:\s*)(.*?)(\s*)$/i", $line, $matches)) $line = $matches[1].$title.$matches[3];
  363. $rawDataNew .= $line;
  364. }
  365. return $rawDataNew;
  366. }
  367. // Return new page
  368. function getPageNew($serverScheme, $serverName, $base, $location, $fileName, $rawData)
  369. {
  370. $page = new YellowPage($this->yellow);
  371. $page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
  372. $page->parseData($rawData, false);
  373. $page->fileName = $this->yellow->toolbox->findFileFromTitle(
  374. $page->get($this->yellow->config->get("webinterfaceFilePrefix")), $page->get("title"), $fileName,
  375. $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
  376. $page->location = $this->yellow->toolbox->findLocationFromFile(
  377. $page->fileName, $this->yellow->config->get("contentDir"),
  378. $this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
  379. $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
  380. if($this->yellow->pages->find($page->location))
  381. {
  382. preg_match("/^(.*?)(\d*)$/", $page->get("title"), $matches);
  383. $titleText = $matches[1];
  384. $titleNumber = $matches[2];
  385. if(strempty($titleNumber)) { $titleNumber = 2; $titleText = $titleText.' '; }
  386. for(; $titleNumber<=999; ++$titleNumber)
  387. {
  388. $page->rawData = $this->updateDataTitle($rawData, $titleText.$titleNumber);
  389. $page->fileName = $this->yellow->toolbox->findFileFromTitle(
  390. $page->get($this->yellow->config->get("webinterfaceFilePrefix")), $titleText.$titleNumber, $fileName,
  391. $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
  392. $page->location = $this->yellow->toolbox->findLocationFromFile(
  393. $page->fileName, $this->yellow->config->get("contentDir"),
  394. $this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
  395. $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
  396. if(!$this->yellow->pages->find($page->location)) { $ok = true; break; }
  397. }
  398. if(!$ok) $page->error(500, "Page '".$page->get("title")."' can not be created!");
  399. }
  400. if(!$this->getUserPermission($page->location, $page->fileName)) $page->error(500, "Page '".$page->get("title")."' is not allowed!");
  401. return $page;
  402. }
  403. // Return modified page
  404. function getPageUpdate($serverScheme, $serverName, $base, $location, $fileName, $rawDataSource, $rawDataEdit, $rawDataFile)
  405. {
  406. $page = new YellowPage($this->yellow);
  407. $page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
  408. $page->parseData($this->merge->merge($rawDataSource, $rawDataEdit, $rawDataFile), false);
  409. if(empty($page->rawData)) $page->error(500, "Page has been modified by someone else!");
  410. if($this->yellow->toolbox->isFileLocation($location) && !$page->isError())
  411. {
  412. $pageSource = new YellowPage($this->yellow);
  413. $pageSource->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
  414. $pageSource->parseData($rawDataSource, false);
  415. $prefix = $this->yellow->config->get("webinterfaceFilePrefix");
  416. if($pageSource->get($prefix)!=$page->get($prefix) || $pageSource->get("title")!=$page->get("title"))
  417. {
  418. $page->fileName = $this->yellow->toolbox->findFileFromTitle(
  419. $page->get($prefix), $page->get("title"), $fileName,
  420. $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
  421. $page->location = $this->yellow->toolbox->findLocationFromFile(
  422. $page->fileName, $this->yellow->config->get("contentDir"),
  423. $this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
  424. $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
  425. if($pageSource->location!=$page->location && $this->yellow->pages->find($page->location))
  426. {
  427. $page->error(500, "Page '".$page->get("title")."' already exists!");
  428. }
  429. }
  430. }
  431. if(!$this->getUserPermission($page->location, $page->fileName)) $page->error(500, "Page '".$page->get("title")."' is not allowed!");
  432. return $page;
  433. }
  434. // Return content data for new page
  435. function getDataNew($title = "")
  436. {
  437. $fileName = $this->yellow->toolbox->findFileFromLocation(
  438. $this->yellow->page->location, $this->yellow->config->get("contentDir"),
  439. $this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
  440. $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
  441. $fileName = $this->yellow->toolbox->findFileNew($fileName,
  442. $this->yellow->config->get("configDir"), $this->yellow->config->get("newPageFile"),
  443. $this->yellow->config->get("contentDefaultFile"));
  444. $fileData = $this->yellow->toolbox->getFileData($fileName);
  445. $fileData = preg_replace("/@date/i", date("Y-m-d"), $fileData);
  446. $fileData = preg_replace("/@username/i", $this->users->getName(), $fileData);
  447. $fileData = preg_replace("/@userlanguage/i", $this->users->getLanguage(), $fileData);
  448. if(!empty($title)) $fileData = $this->updateDataTitle($fileData, $title);
  449. return $fileData;
  450. }
  451. // Return configuration data including information of current user
  452. function getDataConfig()
  453. {
  454. $data = $this->yellow->config->getData("", "Location");
  455. if($this->isUser())
  456. {
  457. $data["userEmail"] = $this->users->email;
  458. $data["userName"] = $this->users->getName();
  459. $data["userLanguage"] = $this->users->getLanguage();
  460. $data["userHome"] = $this->users->getHome();
  461. $data["serverScheme"] = $this->yellow->config->get("serverScheme");
  462. $data["serverName"] = $this->yellow->config->get("serverName");
  463. $data["serverBase"] = $this->yellow->config->get("serverBase");
  464. } else {
  465. $data["loginEmail"] = $this->yellow->config->get("loginEmail");
  466. $data["loginPassword"] = $this->yellow->config->get("loginPassword");
  467. }
  468. return $data;
  469. }
  470. // Check if web interface request
  471. function isActive()
  472. {
  473. return $this->active;
  474. }
  475. // Check if user is logged in
  476. function isUser()
  477. {
  478. return !empty($this->users->email);
  479. }
  480. }
  481. // Yellow web interface users
  482. class YellowWebinterfaceUsers
  483. {
  484. var $yellow; //access to API
  485. var $users; //registered users
  486. var $email; //current user
  487. function __construct($yellow)
  488. {
  489. $this->yellow = $yellow;
  490. $this->users = array();
  491. }
  492. // Load users from file
  493. function load($fileName)
  494. {
  495. $fileData = @file($fileName);
  496. if($fileData)
  497. {
  498. foreach($fileData as $line)
  499. {
  500. if(preg_match("/^\//", $line)) continue;
  501. preg_match("/^(.*?),\s*(.*?),\s*(.*?),\s*(.*?),\s*(.*?)\s*$/", $line, $matches);
  502. if(!empty($matches[1]) && !empty($matches[2]) && !empty($matches[3]) && !empty($matches[4]))
  503. {
  504. $this->set($matches[1], $matches[2], $matches[3], $matches[4], $matches[5]);
  505. if(defined("DEBUG") && DEBUG>=3) echo "YellowWebinterfaceUsers::load email:$matches[1] $matches[3]<br/>\n";
  506. }
  507. }
  508. }
  509. }
  510. // Set user data
  511. function set($email, $hash, $name, $language, $home)
  512. {
  513. $this->users[$email] = array();
  514. $this->users[$email]["email"] = $email;
  515. $this->users[$email]["hash"] = $hash;
  516. $this->users[$email]["name"] = $name;
  517. $this->users[$email]["language"] = $language;
  518. $this->users[$email]["home"] = $home;
  519. }
  520. // Create or update user in file
  521. function createUser($fileName, $email, $hash, $name, $language, $home)
  522. {
  523. $email = strreplaceu(',', '-', $email);
  524. $hash = strreplaceu(',', '-', $hash);
  525. $fileData = @file($fileName);
  526. if($fileData)
  527. {
  528. foreach($fileData as $line)
  529. {
  530. preg_match("/^(.*?),\s*(.*?),\s*(.*?),\s*(.*?),\s*(.*?)\s*$/", $line, $matches);
  531. if(!empty($matches[1]) && !empty($matches[2]) && !empty($matches[3]) && !empty($matches[4]))
  532. {
  533. if($matches[1] == $email)
  534. {
  535. $name = strreplaceu(',', '-', empty($name) ? $matches[3] : $name);
  536. $language = strreplaceu(',', '-', empty($language) ? $matches[4] : $language);
  537. $home = strreplaceu(',', '-', empty($home) ? $matches[5] : $home);
  538. $fileDataNew .= "$email,$hash,$name,$language,$home\n";
  539. $found = true;
  540. continue;
  541. }
  542. }
  543. $fileDataNew .= $line;
  544. }
  545. }
  546. if(!$found)
  547. {
  548. $name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
  549. $language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
  550. $home = strreplaceu(',', '-', empty($home) ? "/" : $home);
  551. $fileDataNew .= "$email,$hash,$name,$language,$home\n";
  552. }
  553. return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
  554. }
  555. // Check user login
  556. function checkUser($email, $password)
  557. {
  558. $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
  559. return $this->isExisting($email) && $this->yellow->toolbox->verifyHash($password, $algorithm, $this->users[$email]["hash"]);
  560. }
  561. // Create browser cookie
  562. function createCookie($cookieName, $email)
  563. {
  564. if($this->isExisting($email))
  565. {
  566. $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation");
  567. $session = $this->yellow->toolbox->createHash($this->users[$email]["hash"], "sha256");
  568. if(empty($session)) $session = "error-hash-algorithm-sha256";
  569. setcookie($cookieName, "$email,$session", time()+60*60*24*30*365, $location,
  570. $this->yellow->config->get("webinterfaceServerName"),
  571. $this->yellow->config->get("webinterfaceServerScheme")=="https");
  572. }
  573. }
  574. // Destroy browser cookie
  575. function destroyCookie($cookieName)
  576. {
  577. $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation");
  578. setcookie($cookieName, "", time()-3600,
  579. $location, $this->yellow->config->get("webinterfaceServerName"),
  580. $this->yellow->config->get("webinterfaceServerScheme")=="https");
  581. }
  582. // Return information from browser cookie
  583. function getCookieInformation($cookie)
  584. {
  585. return explode(',', $cookie, 2);
  586. }
  587. // Check user login from browser cookie
  588. function checkCookie($email, $session)
  589. {
  590. return $this->isExisting($email) && $this->yellow->toolbox->verifyHash($this->users[$email]["hash"], "sha256", $session);
  591. }
  592. // Return user name
  593. function getName($email = "")
  594. {
  595. if(empty($email)) $email = $this->email;
  596. return $this->isExisting($email) ? $this->users[$email]["name"] : "";
  597. }
  598. // Return user language
  599. function getLanguage($email = "")
  600. {
  601. if(empty($email)) $email = $this->email;
  602. return $this->isExisting($email) ? $this->users[$email]["language"] : "";
  603. }
  604. // Return user home
  605. function getHome($email = "")
  606. {
  607. if(empty($email)) $email = $this->email;
  608. return $this->isExisting($email) ? $this->users[$email]["home"] : "";
  609. }
  610. // Check if user exists
  611. function isExisting($email)
  612. {
  613. return !is_null($this->users[$email]);
  614. }
  615. }
  616. // Yellow web interface merge
  617. class YellowWebinterfaceMerge
  618. {
  619. var $yellow; //access to API
  620. const Add = '+'; //merge types
  621. const Modify = '*';
  622. const Remove = '-';
  623. const Same = ' ';
  624. function __construct($yellow)
  625. {
  626. $this->yellow = $yellow;
  627. }
  628. // Merge text, NULL if not possible
  629. function merge($textSource, $textMine, $textYours, $showDiff = false)
  630. {
  631. if($textMine != $textYours)
  632. {
  633. $diffMine = $this->buildDiff($textSource, $textMine);
  634. $diffYours = $this->buildDiff($textSource, $textYours);
  635. $diff = $this->mergeDiff($diffMine, $diffYours);
  636. $output = $this->getOutput($diff, $showDiff);
  637. } else {
  638. $output = $textMine;
  639. }
  640. return $output;
  641. }
  642. // Build differences to common source
  643. function buildDiff($textSource, $textOther)
  644. {
  645. $diff = array();
  646. $lastRemove = -1;
  647. $textStart = 0;
  648. $textSource = $this->yellow->toolbox->getTextLines($textSource);
  649. $textOther = $this->yellow->toolbox->getTextLines($textOther);
  650. $sourceEnd = $sourceSize = count($textSource);
  651. $otherEnd = $otherSize = count($textOther);
  652. while($textStart<$sourceEnd && $textStart<$otherEnd && $textSource[$textStart]==$textOther[$textStart]) ++$textStart;
  653. while($textStart<$sourceEnd && $textStart<$otherEnd && $textSource[$sourceEnd-1]==$textOther[$otherEnd-1])
  654. {
  655. --$sourceEnd; --$otherEnd;
  656. }
  657. for($pos=0; $pos<$textStart; ++$pos) array_push($diff, array(YellowWebinterfaceMerge::Same, $textSource[$pos], false));
  658. $lcs = $this->buildDiffLCS($textSource, $textOther, $textStart, $sourceEnd-$textStart, $otherEnd-$textStart);
  659. for($x=0,$y=0,$xEnd=$otherEnd-$textStart,$yEnd=$sourceEnd-$textStart; $x<$xEnd || $y<$yEnd;)
  660. {
  661. $max = $lcs[$y][$x];
  662. if($y<$yEnd && $lcs[$y+1][$x]==$max)
  663. {
  664. array_push($diff, array(YellowWebinterfaceMerge::Remove, $textSource[$textStart+$y], false));
  665. if($lastRemove == -1) $lastRemove = count($diff)-1;
  666. ++$y;
  667. continue;
  668. }
  669. if($x<$xEnd && $lcs[$y][$x+1]==$max)
  670. {
  671. if($lastRemove==-1 || $diff[$lastRemove][0]!=YellowWebinterfaceMerge::Remove)
  672. {
  673. array_push($diff, array(YellowWebinterfaceMerge::Add, $textOther[$textStart+$x], false));
  674. $lastRemove = -1;
  675. } else {
  676. $diff[$lastRemove] = array(YellowWebinterfaceMerge::Modify, $textOther[$textStart+$x], false);
  677. ++$lastRemove; if(count($diff)==$lastRemove) $lastRemove = -1;
  678. }
  679. ++$x;
  680. continue;
  681. }
  682. array_push($diff, array(YellowWebinterfaceMerge::Same, $textSource[$textStart+$y], false));
  683. $lastRemove = -1;
  684. ++$x;
  685. ++$y;
  686. }
  687. for($pos=$sourceEnd;$pos<$sourceSize; ++$pos) array_push($diff, array(YellowWebinterfaceMerge::Same, $textSource[$pos], false));
  688. return $diff;
  689. }
  690. // Build longest common subsequence
  691. function buildDiffLCS($textSource, $textOther, $textStart, $yEnd, $xEnd)
  692. {
  693. $lcs = array_fill(0, $yEnd+1, array_fill(0, $xEnd+1, 0));
  694. for($y=$yEnd-1; $y>=0; --$y)
  695. {
  696. for($x=$xEnd-1; $x>=0; --$x)
  697. {
  698. if($textSource[$textStart+$y] == $textOther[$textStart+$x])
  699. {
  700. $lcs[$y][$x] = $lcs[$y+1][$x+1]+1;
  701. } else {
  702. $lcs[$y][$x] = max($lcs[$y][$x+1], $lcs[$y+1][$x]);
  703. }
  704. }
  705. }
  706. return $lcs;
  707. }
  708. // Merge differences
  709. function mergeDiff($diffMine, $diffYours)
  710. {
  711. $diff = array();
  712. $posMine = $posYours = 0;
  713. while($posMine<count($diffMine) && $posYours<count($diffYours))
  714. {
  715. $typeMine = $diffMine[$posMine][0];
  716. $typeYours = $diffYours[$posYours][0];
  717. if($typeMine==YellowWebinterfaceMerge::Same)
  718. {
  719. array_push($diff, $diffYours[$posYours]);
  720. } else if($typeYours==YellowWebinterfaceMerge::Same) {
  721. array_push($diff, $diffMine[$posMine]);
  722. } else if($typeMine==YellowWebinterfaceMerge::Add && $typeYours==YellowWebinterfaceMerge::Add) {
  723. $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], false);
  724. } else if($typeMine==YellowWebinterfaceMerge::Modify && $typeYours==YellowWebinterfaceMerge::Modify) {
  725. $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], false);
  726. } else if($typeMine==YellowWebinterfaceMerge::Remove && $typeYours==YellowWebinterfaceMerge::Remove) {
  727. array_push($diff, $diffMine[$posMine]);
  728. } else if($typeMine==YellowWebinterfaceMerge::Add) {
  729. array_push($diff, $diffMine[$posMine]);
  730. } else if($typeYours==YellowWebinterfaceMerge::Add) {
  731. array_push($diff, $diffYours[$posYours]);
  732. } else {
  733. $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], true);
  734. }
  735. if(defined("DEBUG") && DEBUG>=2) echo "YellowWebinterfaceMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
  736. if($typeMine==YellowWebinterfaceMerge::Add || $typeYours==YellowWebinterfaceMerge::Add)
  737. {
  738. if($typeMine==YellowWebinterfaceMerge::Add) ++$posMine;
  739. if($typeYours==YellowWebinterfaceMerge::Add) ++$posYours;
  740. } else {
  741. ++$posMine;
  742. ++$posYours;
  743. }
  744. }
  745. for(;$posMine<count($diffMine); ++$posMine)
  746. {
  747. array_push($diff, $diffMine[$posMine]);
  748. $typeMine = $diffMine[$posMine][0]; $typeYours = ' ';
  749. if(defined("DEBUG") && DEBUG>=2) echo "YellowWebinterfaceMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
  750. }
  751. for(;$posYours<count($diffYours); ++$posYours)
  752. {
  753. array_push($diff, $diffYours[$posYours]);
  754. $typeYours = $diffYours[$posYours][0]; $typeMine = ' ';
  755. if(defined("DEBUG") && DEBUG>=2) echo "YellowWebinterfaceMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
  756. }
  757. return $diff;
  758. }
  759. // Merge potential conflict
  760. function mergeConflict(&$diff, $diffMine, $diffYours, $conflict)
  761. {
  762. if(!$conflict && $diffMine[1]==$diffYours[1])
  763. {
  764. array_push($diff, $diffMine);
  765. } else {
  766. array_push($diff, array($diffMine[0], $diffMine[1], true));
  767. array_push($diff, array($diffYours[0], $diffYours[1], true));
  768. }
  769. }
  770. // Return merged text, NULL if not possible
  771. function getOutput($diff, $showDiff = false)
  772. {
  773. $output = "";
  774. if(!$showDiff)
  775. {
  776. for($i=0; $i<count($diff); ++$i)
  777. {
  778. if($diff[$i][0] != YellowWebinterfaceMerge::Remove) $output .= $diff[$i][1];
  779. $conflict |= $diff[$i][2];
  780. }
  781. } else {
  782. for($i=0; $i<count($diff); ++$i)
  783. {
  784. $output .= $diff[$i][2] ? "! " : $diff[$i][0].' ';
  785. $output .= $diff[$i][1];
  786. }
  787. }
  788. return !$conflict ? $output : NULL;
  789. }
  790. }
  791. $yellow->plugins->register("webinterface", "YellowWebinterface", YellowWebinterface::Version);
  792. ?>