core-webinterface.php 32 KB

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