Source for file browse_help.act.php

Documentation is available at browse_help.act.php

  1. <?php
  2. /**
  3. * @since 12/8/05
  4. * @package polyphony.help
  5. *
  6. * @copyright Copyright &copy; 2005, Middlebury College
  7. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
  8. *
  9. * @version $Id: browse_help.act.php,v 1.10 2007/09/19 14:04:56 adamfranco Exp $
  10. */
  11. require_once(POLYPHONY."/main/library/AbstractActions/Action.class.php");
  12.  
  13. require_once(DOMIT);
  14.  
  15. require_once(HARMONI."GUIManager/Container.class.php");
  16. require_once(HARMONI."GUIManager/Layouts/XLayout.class.php");
  17. require_once(HARMONI."GUIManager/Layouts/YLayout.class.php");
  18. require_once(HARMONI."GUIManager/Components/Block.class.php");
  19. require_once(HARMONI."GUIManager/Components/UnstyledBlock.class.php");
  20. require_once(HARMONI."GUIManager/Components/Menu.class.php");
  21. require_once(HARMONI."GUIManager/Components/MenuItemHeading.class.php");
  22. require_once(HARMONI."GUIManager/Components/MenuItemLink.class.php");
  23. require_once(HARMONI."GUIManager/Components/Heading.class.php");
  24. require_once(HARMONI."GUIManager/Components/Footer.class.php");
  25.  
  26.  
  27. /**
  28. * This is the help-browser action which enables browsing of help documentation.
  29. *
  30. * @since 12/8/05
  31. * @package polyphony.help
  32. *
  33. * @copyright Copyright &copy; 2005, Middlebury College
  34. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
  35. *
  36. * @version $Id: browse_help.act.php,v 1.10 2007/09/19 14:04:56 adamfranco Exp $
  37. */
  38. class browse_helpAction
  39. extends Action
  40. {
  41. /**
  42. * Check Authorizations
  43. *
  44. * @return boolean
  45. * @access public
  46. * @since 4/26/05
  47. */
  48. function isAuthorizedToExecute () {
  49. return true;
  50. }
  51. /**
  52. * Return the heading text for this action, or an empty string.
  53. *
  54. * @return string
  55. * @access public
  56. * @since 4/26/05
  57. */
  58. function getHeadingText () {
  59. return dgettext('polyphony', 'Help Browser');
  60. }
  61. /**
  62. * Execute this action.
  63. *
  64. * @param object Harmoni $harmoni
  65. * @return mixed
  66. * @access public
  67. * @since 4/25/05
  68. */
  69. function execute ( $harmoni ) {
  70. $actionRows = new Container(new YLayout, BLOCK, BACKGROUND_BLOCK);
  71. $heading = dgettext("polyphony", 'Help');
  72. if ($topic = $this->getTopic())
  73. $heading .= " - ".$topic;
  74. $actionRows->add(new Heading($heading, 1));
  75. $actionCols =$actionRows->add(new Container(new XLayout, BLANK, 1));
  76. $actionCols->add($this->getHelpMenu(), "250px", null, null, TOP);
  77. $actionCols->add($this->getTopicContents($this->getTopic()), null, null, null, TOP);
  78. return $actionRows;
  79. }
  80. /**
  81. * Create the main help Menu
  82. *
  83. * @return object Component
  84. * @access public
  85. * @since 12/8/05
  86. */
  87. function getHelpMenu () {
  88. $this->_menu = new Menu(new YLayout, 1);
  89. $toc =$this->getTableOfContents();
  90. $toc->acceptVisitor($this);
  91. return $this->_menu;
  92. }
  93. /**
  94. * Visit a table of contents part and add it to our menue
  95. *
  96. * @param object $tocPart
  97. * @return void
  98. * @access public
  99. * @since 5/31/06
  100. */
  101. function visitTableOfContentsPart ($tocPart) {
  102. $harmoni = Harmoni::instance();
  103. if ($tocPart->topic == $harmoni->config->get('programTitle') && is_null($tocPart->heading)) {
  104. $menuItem = new MenuItemHeading($tocPart->topic, 1);
  105. }else if ($tocPart->topic == $this->getMainTopic() && is_null($tocPart->heading))
  106. $menuItem = new MenuItemLink(
  107. $this->getMainTopic(),
  108. $harmoni->request->quickURL("help", "browse_help"),
  109. (RequestContext::value("topic"))?FALSE:TRUE,
  110. $tocPart->level + 2);
  111. else {
  112. $url = $harmoni->request->quickURL("help", "browse_help", array("topic" => $tocPart->topic, "heading" => $tocPart->heading));
  113. if ($tocPart->heading) {
  114. $url .= "#".strtolower(preg_replace('/[^a-zA-Z0-9_]/', '', strip_tags($tocPart->heading)));
  115. $title = $tocPart->heading;
  116. } else {
  117. $title = $tocPart->topic;
  118. }
  119. $menuItem = new MenuItemLink(
  120. $title,
  121. $url,
  122. (RequestContext::value("topic") == $tocPart->topic && RequestContext::value("heading") == $tocPart->heading)?TRUE:FALSE,
  123. $tocPart->level + 2);
  124. }
  125. $this->_menu->add($menuItem, "100%", null, LEFT, CENTER);
  126. foreach (array_keys($tocPart->children) as $key)
  127. $tocPart->children[$key]->acceptVisitor($this);
  128. $null = null;
  129. return $null;
  130. }
  131. /**
  132. * Answer an array of the Help topics
  133. *
  134. * @return array
  135. * @access public
  136. * @since 12/8/05
  137. */
  138. function getHelpTopics () {
  139. if (!isset($this->_topics)) {
  140. //replace this with config lines.
  141. $harmoni = Harmoni::instance();
  142. if (isset($_SESSION['__help_dirs-'.$harmoni->config->get('programTitle')]) && is_array($_SESSION['__help_dirs-'.$harmoni->config->get('programTitle')]))
  143. $this->_dirs = $_SESSION['__help_dirs-'.$harmoni->config->get('programTitle')];
  144. else
  145. $this->_dirs = array();
  146. $this->_topics = array();
  147. // Set up the table of contents
  148. $this->_tableOfContents = new TableofContentsPart;
  149. $this->_tableOfContents->topic = $harmoni->config->get('programTitle');
  150. $this->_tableOfContents->level = -1;
  151. // traverse through our directories.
  152. $langMan = Services::getService('LanguageManager');
  153. $lang = $langMan->getLanguage();
  154. foreach ($this->_dirs as $key => $dirArray) {
  155. $dir = $dirArray['directory']."/".$lang;
  156. if (is_dir($dir) && $handle = opendir($dir)) {
  157. $this->addTopicsFromDirectory($dir, $handle, $dirArray['urlPath']."/".$lang);
  158. closedir($handle);
  159. } else if (is_dir($dir = $dirArray['directory']."/en_US") && $handle = opendir($dir)) {
  160. $this->addTopicsFromDirectory($dir, $handle, $dirArray['urlPath']."/en_US/");
  161. closedir($handle);
  162. }
  163. }
  164.  
  165. asort($this->_topics);
  166. }
  167. return $this->_topics;
  168. }
  169. /**
  170. * Answer our table of contents object tree
  171. *
  172. * @return object
  173. * @access public
  174. * @since 5/31/06
  175. */
  176. function getTableOfContents () {
  177. if (!isset($this->_tableOfContents))
  178. $this->getHelpTopics();
  179. return $this->_tableOfContents;
  180. }
  181. /**
  182. * Add the topics from a given file
  183. *
  184. * @param string $dir
  185. * @param handle $handle
  186. * @return void
  187. * @access public
  188. * @since 5/31/06
  189. */
  190. function addTopicsFromDirectory ($dir, $handle, $urlPath) {
  191. while (false !== ($file = readdir($handle))) {
  192. $filePath = $dir."/".$file;
  193. $contents = file_get_contents($filePath);
  194. if (preg_match(
  195. "/<title>(.*)<\/title>/i",
  196. $contents,
  197. $matches))
  198. {
  199. $topic = $matches[1];
  200. $this->_topics[$filePath] = $topic;
  201. $part =$this->_tableOfContents->addChild($topic, null, 0, $filePath, $urlPath);
  202. if (preg_match_all(
  203. "/<h([1-6])>(.*)<\/h[1-6]>/i",
  204. $contents,
  205. $matches))
  206. {
  207. for ($i = 0; $i < count($matches[1]); $i++) {
  208. $part->addChild($topic, $matches[2][$i], $matches[1][$i], $filePath, $urlPath);
  209. }
  210. }
  211. }
  212. }
  213. }
  214. /**
  215. * Register a directory of help topics
  216. * This directory should contain sub-directories for each language code.
  217. * The default sub-directory is en_US
  218. *
  219. * @param string $directory
  220. * @return void
  221. * @access public
  222. * @static
  223. * @since 12/9/05
  224. */
  225. function addHelpDirectory ( $directory, $urlPath ) {
  226. $harmoni = Harmoni::instance();
  227. if (!isset($_SESSION['__help_dirs-'.$harmoni->config->get('programTitle')]) || !is_array($_SESSION['__help_dirs-'.$harmoni->config->get('programTitle')]))
  228. $_SESSION['__help_dirs-'.$harmoni->config->get('programTitle')] = array();
  229. if (is_dir($directory)) {
  230. if (!array_key_exists($directory, $_SESSION['__help_dirs-'.$harmoni->config->get('programTitle')])) {
  231. $_SESSION['__help_dirs-'.$harmoni->config->get('programTitle')][$directory] = array('directory' => $directory, 'urlPath' => $urlPath);
  232. }
  233. } else
  234. throwError(new Error("Invalid Help directory, '$directory'", "polyphony.help", true));
  235. }
  236. /**
  237. * Answer the title of the index page
  238. *
  239. * @return string
  240. * @access public
  241. * @since 12/9/05
  242. */
  243. function getMainTopic () {
  244. if (!isset($this->_mainTopic)) {
  245. $topics =$this->getHelpTopics();
  246. foreach ($topics as $file => $topic) {
  247. if (basename($file) == 'index.html')
  248. $this->_mainTopic = $topic;
  249. }
  250. if (!isset($this->_mainTopic))
  251. $this->_mainTopic = dgettext("polyphony", "Main");
  252. }
  253. return $this->_mainTopic;
  254. }
  255. /**
  256. * Answer the current topic
  257. *
  258. * @return string
  259. * @access public
  260. * @since 12/9/05
  261. */
  262. function getTopic () {
  263. if ($topic = RequestContext::value("topic"))
  264. return $topic;
  265. else
  266. return $this->getMainTopic();
  267. }
  268. /**
  269. * Answer the contents of the current topic
  270. *
  271. * @param string $topic
  272. * @return object Component
  273. * @access public
  274. * @since 12/9/05
  275. */
  276. function getTopicContents ($topic) {
  277. $topicContainer = new Container(new YLayout, BLANK, 1);
  278. $tocPart = $this->_tableOfContents->getTableOfContentsPart($topic);
  279. $document =$this->getTopicXmlDocument($topic);
  280. $bodyElements =$document->getElementsByPath("/html/body");
  281. $body =$bodyElements->item(0);
  282. $this->updateSrcTags($document->documentElement, $tocPart->urlPath."/");
  283. // put custom style sheets in the page's head
  284. $headElements =$document->getElementsByPath("/html/head");
  285. $head =$headElements->item(0);
  286. $newHeadText = '';
  287. for ($i = 0; $i < count($head->childNodes); $i++) {
  288. $newHeadText .= $head->childNodes[$i]->toString()."\n\t\t";
  289. }
  290. $harmoni = Harmoni::instance();
  291. $outputHandler =$harmoni->getOutputHandler();
  292. $outputHandler->setHead($outputHandler->getHead().$newHeadText);
  293. ob_start();
  294. for ($i = 0; $i < count($body->childNodes); $i++) {
  295. $element =$body->childNodes[$i];
  296. switch ($element->getTagName()) {
  297.  
  298. case 'h1':
  299. $heading = new Heading($element->getText(), 1);
  300. case 'h2':
  301. if (!isset($heading))
  302. $heading = new Heading($element->getText(), 2);
  303. case 'h3':
  304. if (!isset($heading))
  305. $heading = new Heading($element->getText(), 3);
  306. case 'h4':
  307. if (!isset($heading))
  308. $heading = new Heading($element->getText(), 4);
  309. $heading->setPreHTML(
  310. "<a name=\""
  311. .strtolower(preg_replace('/[^a-zA-Z0-9_]/', '', $element->getText()))
  312. ."\"></a>");
  313. // Finish off our previous block if it had contents.
  314. $previousBlockText = ob_get_clean();
  315. if (strlen(trim($previousBlockText)))
  316. $topicContainer->add(
  317. new Block($this->linkify($previousBlockText),
  318. STANDARD_BLOCK));
  319. // create our heading element
  320. $topicContainer->add($heading);
  321. unset($heading);
  322. // Start a new buffer for the next block contents.
  323. ob_start();
  324. break;
  325. default:
  326. print $element->toString(false, true)."\n";
  327. }
  328. }
  329. $topicContainer->add(new Block($this->linkify(ob_get_clean()), STANDARD_BLOCK));
  330. return $topicContainer;
  331. }
  332. /**
  333. * Convert relative links in src tags to contain the full path needed to
  334. * use them.
  335. *
  336. * @param object
  337. * @return void
  338. * @access public
  339. * @since 5/31/06
  340. */
  341. function updateSrcTags ($element, $path) {
  342. if (method_exists($element, 'hasAttribute')
  343. && $element->hasAttribute('src')
  344. && !preg_match('/([a-z]+:\/\/.+)|(\/.+)/', $element->getAttribute('src')))
  345. {
  346. $element->setAttribute('src',
  347. $path.$element->getAttribute('src'));
  348. }
  349. if (method_exists($element, 'hasAttribute')
  350. && $element->hasAttribute('href')
  351. && !preg_match('/([a-z]+:\/\/.+)|(\/.+)/', $element->getAttribute('href')))
  352. {
  353. $element->setAttribute('href',
  354. $path.$element->getAttribute('href'));
  355. }
  356. for ($i = 0; $i < count($element->childNodes); $i++)
  357. $this->updateSrcTags($element->childNodes[$i], $path);
  358. }
  359. /**
  360. * Convert links of the form [[title]] or [[title#heading]] to html links
  361. * of the form <a href='link'>title: heading</a>
  362. *
  363. * @param string $inputText
  364. * @return string
  365. * @access public
  366. * @since 1/6/06
  367. */
  368. function linkify ( $inputText ) {
  369. // Find all link-holders
  370. $regex =
  371. '/
  372. \[\[
  373. ([^\]\#]+) # Match the title
  374. (?:
  375. \#
  376. ([^\]]+) # Match the heading
  377. )?
  378. \]\]
  379. /ix';
  380. if (preg_match_all($regex, $inputText, $matches)) {
  381. $harmoni = Harmoni::instance();
  382. for ($i = 0; $i < count($matches[0]); $i++) {
  383. ob_start();
  384. print '<a href="';
  385. print $harmoni->request->quickURL(
  386. "help", "browse_help",
  387. array("topic" => $matches[1][$i]));
  388. print '#';
  389. print strtolower(preg_replace('/[^a-zA-Z0-9_]/', '', $matches[2][$i]));
  390. print '">';
  391. print $matches[1][$i];
  392. if ($matches[2][$i])
  393. print ': '.$matches[2][$i];
  394. print '</a>';
  395. $inputText = str_replace($matches[0][$i], ob_get_clean(), $inputText);
  396. }
  397. }
  398. return $inputText;
  399. }
  400. /**
  401. * Answer a DOMIT XML Document for the given topic
  402. *
  403. * @param string $topic
  404. * @return object DOMIT_Document
  405. * @access public
  406. * @since 12/9/05
  407. */
  408. function getTopicXmlDocument ($topic) {
  409. $document = new DOMIT_Document();
  410. $tocPart = $this->_tableOfContents->getTableOfContentsPart($topic);
  411. if (!$tocPart->file || !file_exists($tocPart->file)) {
  412. ob_start();
  413. print "<html>\n";
  414. print " <head>\n";
  415. print " <title>";
  416. print dgettext("polyphony", "Topic Not Found");
  417. print "</title>\n";
  418. print " </head>\n";
  419. print " <body>\n";
  420. print " <h1>";
  421. print dgettext("polyphony", "Topic Not Found");
  422. print "</h1>\n";
  423. print " <p>";
  424. print dgettext("polyphony", "The topic that you requested was not found.");
  425. print "</p>\n";
  426. print " </body>\n";
  427. print "</html>\n";
  428. $document->parseXML(ob_get_contents());
  429. ob_end_clean();
  430. } else {
  431. $document->loadXML($tocPart->file);
  432. }
  433. return $document;
  434. }
  435. }
  436.  
  437.  
  438. /**
  439. * <##>
  440. *
  441. * @since 5/31/06
  442. * @package polyphony.help
  443. *
  444. * @copyright Copyright &copy; 2005, Middlebury College
  445. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
  446. *
  447. * @version $Id: browse_help.act.php,v 1.10 2007/09/19 14:04:56 adamfranco Exp $
  448. */
  449. class TableOfContentsPart {
  450. /**
  451. * Constructor
  452. *
  453. * @param <##>
  454. * @return <##>
  455. * @access public
  456. * @since 5/31/06
  457. */
  458. function TableOfContentsPart () {
  459. $this->level = 0;
  460. $this->topic = null;
  461. $this->heading = null;
  462. $this->file = null;
  463. $this->urlPath = null;
  464. $this->children = array();
  465. }
  466. /**
  467. * Add a child part to our children or ourselves, or return false
  468. * if not possible (i.e. h1 being put under an h3)
  469. *
  470. * @param string $topic
  471. * @param string $heading
  472. * @param integer $level
  473. * @return void
  474. * @access public
  475. * @since 5/31/06
  476. */
  477. function addChild ($topic, $heading, $level, $file, $urlPath) {
  478. $false = false;
  479. // if the level is greater or equal to ours, it is a sibling or uncle
  480. if ($level <= $this->level)
  481. return $false;
  482. // see if our last child can handle it (to append to that child)
  483. if (count($this->children))
  484. {
  485. $keys = array_keys($this->children);
  486. $part =$this->children[$keys[count($keys) - 1]]->addChild($topic, $heading, $level, $file, $urlPath);
  487. if ($part)
  488. return $part;
  489. }
  490. // if it couldn't be appended to our last child, add it as a new child
  491. $part = new TableofContentsPart;
  492. $part->topic = $topic;
  493. $part->heading = $heading;
  494. $part->level = $level;
  495. $part->file = $file;
  496. $part->urlPath = $urlPath;
  497. $this->children[$topic.$heading] =$part;
  498. if ($level == 0)
  499. ksort($this->children);
  500. return $part;
  501. }
  502. /**
  503. * Accept a visitor
  504. *
  505. * @param object $visitor
  506. * @return mixed
  507. * @access public
  508. * @since 5/31/06
  509. */
  510. function acceptVisitor ($visitor) {
  511. $result =$visitor->visitTableOfContentsPart($this);
  512. return $result;
  513. }
  514. /**
  515. * Answer the table of contents part that matches the given topic/heading
  516. *
  517. * @param string $topic
  518. * @param string $heading
  519. * @return object
  520. * @access public
  521. * @since 5/31/06
  522. */
  523. function getTableOfContentsPart ($topic, $heading = null) {
  524. if ($topic == $this->topic && $heading == $this->heading)
  525. return $this;
  526. foreach (array_keys($this->children) as $key) {
  527. $result =$this->children[$key]->getTableOfContentsPart($topic, $heading);
  528. if ($result)
  529. return $result;
  530. }
  531. $false = false;
  532. return $false;
  533. }
  534. }
  535.  
  536. ?>

Documentation generated on Wed, 19 Sep 2007 10:21:54 -0400 by phpDocumentor 1.3.0RC3