webinterface.php 35 KB

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