PageRenderTime 55ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/action.php

https://github.com/Br3nda/laconica
PHP | 1105 lines | 620 code | 83 blank | 402 comment | 75 complexity | b1cf7a4edbc67a76fcc535fba03f9ec3 MD5 | raw file
Possible License(s): AGPL-3.0
  1. <?php
  2. /**
  3. * Laconica, the distributed open-source microblogging tool
  4. *
  5. * Base class for all actions (~views)
  6. *
  7. * PHP version 5
  8. *
  9. * LICENCE: This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. * @category Action
  23. * @package Laconica
  24. * @author Evan Prodromou <evan@controlyourself.ca>
  25. * @author Sarven Capadisli <csarven@controlyourself.ca>
  26. * @copyright 2008 Control Yourself, Inc.
  27. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  28. * @link http://laconi.ca/
  29. */
  30. if (!defined('LACONICA')) {
  31. exit(1);
  32. }
  33. require_once INSTALLDIR.'/lib/noticeform.php';
  34. require_once INSTALLDIR.'/lib/htmloutputter.php';
  35. /**
  36. * Base class for all actions
  37. *
  38. * This is the base class for all actions in the package. An action is
  39. * more or less a "view" in an MVC framework.
  40. *
  41. * Actions are responsible for extracting and validating parameters; using
  42. * model classes to read and write to the database; and doing ouput.
  43. *
  44. * @category Output
  45. * @package Laconica
  46. * @author Evan Prodromou <evan@controlyourself.ca>
  47. * @author Sarven Capadisli <csarven@controlyourself.ca>
  48. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  49. * @link http://laconi.ca/
  50. *
  51. * @see HTMLOutputter
  52. */
  53. class Action extends HTMLOutputter // lawsuit
  54. {
  55. var $args;
  56. /**
  57. * Constructor
  58. *
  59. * Just wraps the HTMLOutputter constructor.
  60. *
  61. * @param string $output URI to output to, default = stdout
  62. * @param boolean $indent Whether to indent output, default true
  63. *
  64. * @see XMLOutputter::__construct
  65. * @see HTMLOutputter::__construct
  66. */
  67. function __construct($output='php://output', $indent=true)
  68. {
  69. parent::__construct($output, $indent);
  70. }
  71. /**
  72. * For initializing members of the class.
  73. *
  74. * @param array $argarray misc. arguments
  75. *
  76. * @return boolean true
  77. */
  78. function prepare($argarray)
  79. {
  80. $this->args =& common_copy_args($argarray);
  81. return true;
  82. }
  83. /**
  84. * Show page, a template method.
  85. *
  86. * @return nothing
  87. */
  88. function showPage()
  89. {
  90. if (Event::handle('StartShowHTML', array($this))) {
  91. $this->startHTML();
  92. Event::handle('EndShowHTML', array($this));
  93. }
  94. if (Event::handle('StartShowHead', array($this))) {
  95. $this->showHead();
  96. Event::handle('EndShowHead', array($this));
  97. }
  98. if (Event::handle('StartShowBody', array($this))) {
  99. $this->showBody();
  100. Event::handle('EndShowBody', array($this));
  101. }
  102. if (Event::handle('StartEndHTML', array($this))) {
  103. $this->endHTML();
  104. Event::handle('EndEndHTML', array($this));
  105. }
  106. }
  107. /**
  108. * Show head, a template method.
  109. *
  110. * @return nothing
  111. */
  112. function showHead()
  113. {
  114. // XXX: attributes (profile?)
  115. $this->elementStart('head');
  116. $this->showTitle();
  117. $this->showShortcutIcon();
  118. $this->showStylesheets();
  119. $this->showScripts();
  120. $this->showOpenSearch();
  121. $this->showFeeds();
  122. $this->showDescription();
  123. $this->extraHead();
  124. $this->elementEnd('head');
  125. }
  126. /**
  127. * Show title, a template method.
  128. *
  129. * @return nothing
  130. */
  131. function showTitle()
  132. {
  133. $this->element('title', null,
  134. sprintf(_("%s - %s"),
  135. $this->title(),
  136. common_config('site', 'name')));
  137. }
  138. /**
  139. * Returns the page title
  140. *
  141. * SHOULD overload
  142. *
  143. * @return string page title
  144. */
  145. function title()
  146. {
  147. return _("Untitled page");
  148. }
  149. /**
  150. * Show themed shortcut icon
  151. *
  152. * @return nothing
  153. */
  154. function showShortcutIcon()
  155. {
  156. if (is_readable(INSTALLDIR . '/theme/' . common_config('site', 'theme') . '/favicon.ico')) {
  157. $this->element('link', array('rel' => 'shortcut icon',
  158. 'href' => theme_path('favicon.ico')));
  159. } else {
  160. $this->element('link', array('rel' => 'shortcut icon',
  161. 'href' => common_path('favicon.ico')));
  162. }
  163. if (common_config('site', 'mobile')) {
  164. if (is_readable(INSTALLDIR . '/theme/' . common_config('site', 'theme') . '/apple-touch-icon.png')) {
  165. $this->element('link', array('rel' => 'apple-touch-icon',
  166. 'href' => theme_path('apple-touch-icon.png')));
  167. } else {
  168. $this->element('link', array('rel' => 'apple-touch-icon',
  169. 'href' => common_path('apple-touch-icon.png')));
  170. }
  171. }
  172. }
  173. /**
  174. * Show stylesheets
  175. *
  176. * @return nothing
  177. */
  178. function showStylesheets()
  179. {
  180. if (Event::handle('StartShowStyles', array($this))) {
  181. if (Event::handle('StartShowLaconicaStyles', array($this))) {
  182. $this->element('link', array('rel' => 'stylesheet',
  183. 'type' => 'text/css',
  184. 'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
  185. 'media' => 'screen, projection, tv'));
  186. if (common_config('site', 'mobile')) {
  187. $this->element('link', array('rel' => 'stylesheet',
  188. 'type' => 'text/css',
  189. 'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION,
  190. // TODO: "handheld" CSS for other mobile devices
  191. 'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit
  192. }
  193. $this->element('link', array('rel' => 'stylesheet',
  194. 'type' => 'text/css',
  195. 'href' => theme_path('css/print.css', 'base') . '?version=' . LACONICA_VERSION,
  196. 'media' => 'print'));
  197. Event::handle('EndShowLaconicaStyles', array($this));
  198. }
  199. if (Event::handle('StartShowUAStyles', array($this))) {
  200. $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
  201. 'href="'.theme_path('css/ie.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
  202. foreach (array(6,7) as $ver) {
  203. if (file_exists(theme_file('css/ie'.$ver.'.css', 'base'))) {
  204. // Yes, IE people should be put in jail.
  205. $this->comment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
  206. 'href="'.theme_path('css/ie'.$ver.'.css', 'base').'?version='.LACONICA_VERSION.'" /><![endif]');
  207. }
  208. }
  209. $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
  210. 'href="'.theme_path('css/ie.css', null).'?version='.LACONICA_VERSION.'" /><![endif]');
  211. Event::handle('EndShowUAStyles', array($this));
  212. }
  213. if (Event::handle('StartShowDesign', array($this))) {
  214. $user = common_current_user();
  215. if (empty($user) || $user->viewdesigns) {
  216. $design = $this->getDesign();
  217. if (!empty($design)) {
  218. $design->showCSS($this);
  219. }
  220. }
  221. Event::handle('EndShowDesign', array($this));
  222. }
  223. Event::handle('EndShowStyles', array($this));
  224. }
  225. }
  226. /**
  227. * Show javascript headers
  228. *
  229. * @return nothing
  230. */
  231. function showScripts()
  232. {
  233. if (Event::handle('StartShowScripts', array($this))) {
  234. if (Event::handle('StartShowJQueryScripts', array($this))) {
  235. $this->element('script', array('type' => 'text/javascript',
  236. 'src' => common_path('js/jquery.min.js')),
  237. ' ');
  238. $this->element('script', array('type' => 'text/javascript',
  239. 'src' => common_path('js/jquery.form.js')),
  240. ' ');
  241. $this->element('script', array('type' => 'text/javascript',
  242. 'src' => common_path('js/jquery.joverlay.min.js')),
  243. ' ');
  244. Event::handle('EndShowJQueryScripts', array($this));
  245. }
  246. if (Event::handle('StartShowLaconicaScripts', array($this))) {
  247. $this->element('script', array('type' => 'text/javascript',
  248. 'src' => common_path('js/xbImportNode.js')),
  249. ' ');
  250. $this->element('script', array('type' => 'text/javascript',
  251. 'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
  252. ' ');
  253. // Frame-busting code to avoid clickjacking attacks.
  254. $this->element('script', array('type' => 'text/javascript'),
  255. 'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
  256. Event::handle('EndShowLaconicaScripts', array($this));
  257. }
  258. Event::handle('EndShowScripts', array($this));
  259. }
  260. }
  261. /**
  262. * Show OpenSearch headers
  263. *
  264. * @return nothing
  265. */
  266. function showOpenSearch()
  267. {
  268. $this->element('link', array('rel' => 'search',
  269. 'type' => 'application/opensearchdescription+xml',
  270. 'href' => common_local_url('opensearch', array('type' => 'people')),
  271. 'title' => common_config('site', 'name').' People Search'));
  272. $this->element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
  273. 'href' => common_local_url('opensearch', array('type' => 'notice')),
  274. 'title' => common_config('site', 'name').' Notice Search'));
  275. }
  276. /**
  277. * Show feed headers
  278. *
  279. * MAY overload
  280. *
  281. * @return nothing
  282. */
  283. function showFeeds()
  284. {
  285. $feeds = $this->getFeeds();
  286. if ($feeds) {
  287. foreach ($feeds as $feed) {
  288. $this->element('link', array('rel' => $feed->rel(),
  289. 'href' => $feed->url,
  290. 'type' => $feed->mimeType(),
  291. 'title' => $feed->title));
  292. }
  293. }
  294. }
  295. /**
  296. * Show description.
  297. *
  298. * SHOULD overload
  299. *
  300. * @return nothing
  301. */
  302. function showDescription()
  303. {
  304. // does nothing by default
  305. }
  306. /**
  307. * Show extra stuff in <head>.
  308. *
  309. * MAY overload
  310. *
  311. * @return nothing
  312. */
  313. function extraHead()
  314. {
  315. // does nothing by default
  316. }
  317. /**
  318. * Show body.
  319. *
  320. * Calls template methods
  321. *
  322. * @return nothing
  323. */
  324. function showBody()
  325. {
  326. $this->elementStart('body', (common_current_user()) ? array('id' => $this->trimmed('action'),
  327. 'class' => 'user_in')
  328. : array('id' => $this->trimmed('action')));
  329. $this->elementStart('div', array('id' => 'wrap'));
  330. if (Event::handle('StartShowHeader', array($this))) {
  331. $this->showHeader();
  332. Event::handle('EndShowHeader', array($this));
  333. }
  334. $this->showCore();
  335. if (Event::handle('StartShowFooter', array($this))) {
  336. $this->showFooter();
  337. Event::handle('EndShowFooter', array($this));
  338. }
  339. $this->elementEnd('div');
  340. $this->elementEnd('body');
  341. }
  342. /**
  343. * Show header of the page.
  344. *
  345. * Calls template methods
  346. *
  347. * @return nothing
  348. */
  349. function showHeader()
  350. {
  351. $this->elementStart('div', array('id' => 'header'));
  352. $this->showLogo();
  353. $this->showPrimaryNav();
  354. $this->showSiteNotice();
  355. if (common_logged_in()) {
  356. $this->showNoticeForm();
  357. } else {
  358. $this->showAnonymousMessage();
  359. }
  360. $this->elementEnd('div');
  361. }
  362. /**
  363. * Show configured logo.
  364. *
  365. * @return nothing
  366. */
  367. function showLogo()
  368. {
  369. $this->elementStart('address', array('id' => 'site_contact',
  370. 'class' => 'vcard'));
  371. if (Event::handle('StartAddressData', array($this))) {
  372. $this->elementStart('a', array('class' => 'url home bookmark',
  373. 'href' => common_local_url('public')));
  374. if (common_config('site', 'logo') || file_exists(theme_file('logo.png'))) {
  375. $this->element('img', array('class' => 'logo photo',
  376. 'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : theme_path('logo.png'),
  377. 'alt' => common_config('site', 'name')));
  378. }
  379. $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
  380. $this->elementEnd('a');
  381. Event::handle('EndAddressData', array($this));
  382. }
  383. $this->elementEnd('address');
  384. }
  385. /**
  386. * Show primary navigation.
  387. *
  388. * @return nothing
  389. */
  390. function showPrimaryNav()
  391. {
  392. $user = common_current_user();
  393. $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
  394. $this->element('dt', null, _('Primary site navigation'));
  395. $this->elementStart('dd');
  396. $this->elementStart('ul', array('class' => 'nav'));
  397. if (Event::handle('StartPrimaryNav', array($this))) {
  398. if ($user) {
  399. $this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
  400. _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
  401. $this->menuItem(common_local_url('profilesettings'),
  402. _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
  403. if (common_config('xmpp', 'enabled')) {
  404. $this->menuItem(common_local_url('imsettings'),
  405. _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
  406. } else {
  407. $this->menuItem(common_local_url('smssettings'),
  408. _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
  409. }
  410. if (common_config('invite', 'enabled')) {
  411. $this->menuItem(common_local_url('invite'),
  412. _('Invite'),
  413. sprintf(_('Invite friends and colleagues to join you on %s'),
  414. common_config('site', 'name')),
  415. false, 'nav_invitecontact');
  416. }
  417. $this->menuItem(common_local_url('logout'),
  418. _('Logout'), _('Logout from the site'), false, 'nav_logout');
  419. }
  420. else {
  421. if (!common_config('site', 'closed')) {
  422. $this->menuItem(common_local_url('register'),
  423. _('Register'), _('Create an account'), false, 'nav_register');
  424. }
  425. $this->menuItem(common_local_url('login'),
  426. _('Login'), _('Login to the site'), false, 'nav_login');
  427. }
  428. $this->menuItem(common_local_url('doc', array('title' => 'help')),
  429. _('Help'), _('Help me!'), false, 'nav_help');
  430. $this->menuItem(common_local_url('peoplesearch'),
  431. _('Search'), _('Search for people or text'), false, 'nav_search');
  432. Event::handle('EndPrimaryNav', array($this));
  433. }
  434. $this->elementEnd('ul');
  435. $this->elementEnd('dd');
  436. $this->elementEnd('dl');
  437. }
  438. /**
  439. * Show site notice.
  440. *
  441. * @return nothing
  442. */
  443. function showSiteNotice()
  444. {
  445. // Revist. Should probably do an hAtom pattern here
  446. $text = common_config('site', 'notice');
  447. if ($text) {
  448. $this->elementStart('dl', array('id' => 'site_notice',
  449. 'class' => 'system_notice'));
  450. $this->element('dt', null, _('Site notice'));
  451. $this->elementStart('dd', null);
  452. $this->raw($text);
  453. $this->elementEnd('dd');
  454. $this->elementEnd('dl');
  455. }
  456. }
  457. /**
  458. * Show notice form.
  459. *
  460. * MAY overload if no notice form needed... or direct message box????
  461. *
  462. * @return nothing
  463. */
  464. function showNoticeForm()
  465. {
  466. $notice_form = new NoticeForm($this);
  467. $notice_form->show();
  468. }
  469. /**
  470. * Show anonymous message.
  471. *
  472. * SHOULD overload
  473. *
  474. * @return nothing
  475. */
  476. function showAnonymousMessage()
  477. {
  478. // needs to be defined by the class
  479. }
  480. /**
  481. * Show core.
  482. *
  483. * Shows local navigation, content block and aside.
  484. *
  485. * @return nothing
  486. */
  487. function showCore()
  488. {
  489. $this->elementStart('div', array('id' => 'core'));
  490. if (Event::handle('StartShowLocalNavBlock', array($this))) {
  491. $this->showLocalNavBlock();
  492. Event::handle('EndShowLocalNavBlock', array($this));
  493. }
  494. if (Event::handle('StartShowContentBlock', array($this))) {
  495. $this->showContentBlock();
  496. Event::handle('EndShowContentBlock', array($this));
  497. }
  498. $this->showAside();
  499. $this->elementEnd('div');
  500. }
  501. /**
  502. * Show local navigation block.
  503. *
  504. * @return nothing
  505. */
  506. function showLocalNavBlock()
  507. {
  508. $this->elementStart('dl', array('id' => 'site_nav_local_views'));
  509. $this->element('dt', null, _('Local views'));
  510. $this->elementStart('dd');
  511. $this->showLocalNav();
  512. $this->elementEnd('dd');
  513. $this->elementEnd('dl');
  514. }
  515. /**
  516. * Show local navigation.
  517. *
  518. * SHOULD overload
  519. *
  520. * @return nothing
  521. */
  522. function showLocalNav()
  523. {
  524. // does nothing by default
  525. }
  526. /**
  527. * Show content block.
  528. *
  529. * @return nothing
  530. */
  531. function showContentBlock()
  532. {
  533. $this->elementStart('div', array('id' => 'content'));
  534. $this->showPageTitle();
  535. $this->showPageNoticeBlock();
  536. $this->elementStart('div', array('id' => 'content_inner'));
  537. // show the actual content (forms, lists, whatever)
  538. $this->showContent();
  539. $this->elementEnd('div');
  540. $this->elementEnd('div');
  541. }
  542. /**
  543. * Show page title.
  544. *
  545. * @return nothing
  546. */
  547. function showPageTitle()
  548. {
  549. $this->element('h1', null, $this->title());
  550. }
  551. /**
  552. * Show page notice block.
  553. *
  554. * Only show the block if a subclassed action has overrided
  555. * Action::showPageNotice(), or an event handler is registered for
  556. * the StartShowPageNotice event, in which case we assume the
  557. * 'page_notice' definition list is desired. This is to prevent
  558. * empty 'page_notice' definition lists from being output everywhere.
  559. *
  560. * @return nothing
  561. */
  562. function showPageNoticeBlock()
  563. {
  564. $rmethod = new ReflectionMethod($this, 'showPageNotice');
  565. $dclass = $rmethod->getDeclaringClass()->getName();
  566. if ($dclass != 'Action' || Event::hasHandler('StartShowPageNotice')) {
  567. $this->elementStart('dl', array('id' => 'page_notice',
  568. 'class' => 'system_notice'));
  569. $this->element('dt', null, _('Page notice'));
  570. $this->elementStart('dd');
  571. if (Event::handle('StartShowPageNotice', array($this))) {
  572. $this->showPageNotice();
  573. Event::handle('EndShowPageNotice', array($this));
  574. }
  575. $this->elementEnd('dd');
  576. $this->elementEnd('dl');
  577. }
  578. }
  579. /**
  580. * Show page notice.
  581. *
  582. * SHOULD overload (unless there's not a notice)
  583. *
  584. * @return nothing
  585. */
  586. function showPageNotice()
  587. {
  588. }
  589. /**
  590. * Show content.
  591. *
  592. * MUST overload (unless there's not a notice)
  593. *
  594. * @return nothing
  595. */
  596. function showContent()
  597. {
  598. }
  599. /**
  600. * Show Aside.
  601. *
  602. * @return nothing
  603. */
  604. function showAside()
  605. {
  606. $this->elementStart('div', array('id' => 'aside_primary',
  607. 'class' => 'aside'));
  608. if (Event::handle('StartShowExportData', array($this))) {
  609. $this->showExportData();
  610. Event::handle('EndShowExportData', array($this));
  611. }
  612. if (Event::handle('StartShowSections', array($this))) {
  613. $this->showSections();
  614. Event::handle('EndShowSections', array($this));
  615. }
  616. $this->elementEnd('div');
  617. }
  618. /**
  619. * Show export data feeds.
  620. *
  621. * @return void
  622. */
  623. function showExportData()
  624. {
  625. $feeds = $this->getFeeds();
  626. if ($feeds) {
  627. $fl = new FeedList($this);
  628. $fl->show($feeds);
  629. }
  630. }
  631. /**
  632. * Show sections.
  633. *
  634. * SHOULD overload
  635. *
  636. * @return nothing
  637. */
  638. function showSections()
  639. {
  640. // for each section, show it
  641. }
  642. /**
  643. * Show footer.
  644. *
  645. * @return nothing
  646. */
  647. function showFooter()
  648. {
  649. $this->elementStart('div', array('id' => 'footer'));
  650. $this->showSecondaryNav();
  651. $this->showLicenses();
  652. $this->elementEnd('div');
  653. }
  654. /**
  655. * Show secondary navigation.
  656. *
  657. * @return nothing
  658. */
  659. function showSecondaryNav()
  660. {
  661. $this->elementStart('dl', array('id' => 'site_nav_global_secondary'));
  662. $this->element('dt', null, _('Secondary site navigation'));
  663. $this->elementStart('dd', null);
  664. $this->elementStart('ul', array('class' => 'nav'));
  665. if (Event::handle('StartSecondaryNav', array($this))) {
  666. $this->menuItem(common_local_url('doc', array('title' => 'help')),
  667. _('Help'));
  668. $this->menuItem(common_local_url('doc', array('title' => 'about')),
  669. _('About'));
  670. $this->menuItem(common_local_url('doc', array('title' => 'faq')),
  671. _('FAQ'));
  672. $bb = common_config('site', 'broughtby');
  673. if (!empty($bb)) {
  674. $this->menuItem(common_local_url('doc', array('title' => 'tos')),
  675. _('TOS'));
  676. }
  677. $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
  678. _('Privacy'));
  679. $this->menuItem(common_local_url('doc', array('title' => 'source')),
  680. _('Source'));
  681. $this->menuItem(common_local_url('doc', array('title' => 'contact')),
  682. _('Contact'));
  683. $this->menuItem(common_local_url('doc', array('title' => 'badge')),
  684. _('Badge'));
  685. Event::handle('EndSecondaryNav', array($this));
  686. }
  687. $this->elementEnd('ul');
  688. $this->elementEnd('dd');
  689. $this->elementEnd('dl');
  690. }
  691. /**
  692. * Show licenses.
  693. *
  694. * @return nothing
  695. */
  696. function showLicenses()
  697. {
  698. $this->elementStart('dl', array('id' => 'licenses'));
  699. $this->showLaconicaLicense();
  700. $this->showContentLicense();
  701. $this->elementEnd('dl');
  702. }
  703. /**
  704. * Show Laconica license.
  705. *
  706. * @return nothing
  707. */
  708. function showLaconicaLicense()
  709. {
  710. $this->element('dt', array('id' => 'site_laconica_license'), _('Laconica software license'));
  711. $this->elementStart('dd', null);
  712. if (common_config('site', 'broughtby')) {
  713. $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%). ');
  714. } else {
  715. $instr = _('**%%site.name%%** is a microblogging service. ');
  716. }
  717. $instr .= sprintf(_('It runs the [Laconica](http://laconi.ca/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), LACONICA_VERSION);
  718. $output = common_markup_to_html($instr);
  719. $this->raw($output);
  720. $this->elementEnd('dd');
  721. // do it
  722. }
  723. /**
  724. * Show content license.
  725. *
  726. * @return nothing
  727. */
  728. function showContentLicense()
  729. {
  730. $this->element('dt', array('id' => 'site_content_license'), _('Laconica software license'));
  731. $this->elementStart('dd', array('id' => 'site_content_license_cc'));
  732. $this->elementStart('p');
  733. $this->element('img', array('id' => 'license_cc',
  734. 'src' => common_config('license', 'image'),
  735. 'alt' => common_config('license', 'title'),
  736. 'width' => '80',
  737. 'height' => '15'));
  738. //TODO: This is dirty: i18n
  739. $this->text(_('All '.common_config('site', 'name').' content and data are available under the '));
  740. $this->element('a', array('class' => 'license',
  741. 'rel' => 'external license',
  742. 'href' => common_config('license', 'url')),
  743. common_config('license', 'title'));
  744. $this->text(_('license.'));
  745. $this->elementEnd('p');
  746. $this->elementEnd('dd');
  747. }
  748. /**
  749. * Return last modified, if applicable.
  750. *
  751. * MAY override
  752. *
  753. * @return string last modified http header
  754. */
  755. function lastModified()
  756. {
  757. // For comparison with If-Last-Modified
  758. // If not applicable, return null
  759. return null;
  760. }
  761. /**
  762. * Return etag, if applicable.
  763. *
  764. * MAY override
  765. *
  766. * @return string etag http header
  767. */
  768. function etag()
  769. {
  770. return null;
  771. }
  772. /**
  773. * Return true if read only.
  774. *
  775. * MAY override
  776. *
  777. * @param array $args other arguments
  778. *
  779. * @return boolean is read only action?
  780. */
  781. function isReadOnly($args)
  782. {
  783. return false;
  784. }
  785. /**
  786. * Returns query argument or default value if not found
  787. *
  788. * @param string $key requested argument
  789. * @param string $def default value to return if $key is not provided
  790. *
  791. * @return boolean is read only action?
  792. */
  793. function arg($key, $def=null)
  794. {
  795. if (array_key_exists($key, $this->args)) {
  796. return $this->args[$key];
  797. } else {
  798. return $def;
  799. }
  800. }
  801. /**
  802. * Returns trimmed query argument or default value if not found
  803. *
  804. * @param string $key requested argument
  805. * @param string $def default value to return if $key is not provided
  806. *
  807. * @return boolean is read only action?
  808. */
  809. function trimmed($key, $def=null)
  810. {
  811. $arg = $this->arg($key, $def);
  812. return is_string($arg) ? trim($arg) : $arg;
  813. }
  814. /**
  815. * Handler method
  816. *
  817. * @param array $argarray is ignored since it's now passed in in prepare()
  818. *
  819. * @return boolean is read only action?
  820. */
  821. function handle($argarray=null)
  822. {
  823. $lm = $this->lastModified();
  824. $etag = $this->etag();
  825. if ($etag) {
  826. header('ETag: ' . $etag);
  827. }
  828. if ($lm) {
  829. header('Last-Modified: ' . date(DATE_RFC1123, $lm));
  830. if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
  831. $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
  832. $ims = strtotime($if_modified_since);
  833. if ($lm <= $ims) {
  834. $if_none_match = (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER)) ?
  835. $_SERVER['HTTP_IF_NONE_MATCH'] : null;
  836. if (!$if_none_match ||
  837. !$etag ||
  838. $this->_hasEtag($etag, $if_none_match)) {
  839. header('HTTP/1.1 304 Not Modified');
  840. // Better way to do this?
  841. exit(0);
  842. }
  843. }
  844. }
  845. }
  846. }
  847. /**
  848. * HasĀ etag? (private)
  849. *
  850. * @param string $etag etag http header
  851. * @param string $if_none_match ifNoneMatch http header
  852. *
  853. * @return boolean
  854. */
  855. function _hasEtag($etag, $if_none_match)
  856. {
  857. $etags = explode(',', $if_none_match);
  858. return in_array($etag, $etags) || in_array('*', $etags);
  859. }
  860. /**
  861. * Boolean understands english (yes, no, true, false)
  862. *
  863. * @param string $key query key we're interested in
  864. * @param string $def default value
  865. *
  866. * @return boolean interprets yes/no strings as boolean
  867. */
  868. function boolean($key, $def=false)
  869. {
  870. $arg = strtolower($this->trimmed($key));
  871. if (is_null($arg)) {
  872. return $def;
  873. } else if (in_array($arg, array('true', 'yes', '1'))) {
  874. return true;
  875. } else if (in_array($arg, array('false', 'no', '0'))) {
  876. return false;
  877. } else {
  878. return $def;
  879. }
  880. }
  881. /**
  882. * Server error
  883. *
  884. * @param string $msg error message to display
  885. * @param integer $code http error code, 500 by default
  886. *
  887. * @return nothing
  888. */
  889. function serverError($msg, $code=500)
  890. {
  891. $action = $this->trimmed('action');
  892. common_debug("Server error '$code' on '$action': $msg", __FILE__);
  893. throw new ServerException($msg, $code);
  894. }
  895. /**
  896. * Client error
  897. *
  898. * @param string $msg error message to display
  899. * @param integer $code http error code, 400 by default
  900. *
  901. * @return nothing
  902. */
  903. function clientError($msg, $code=400)
  904. {
  905. $action = $this->trimmed('action');
  906. common_debug("User error '$code' on '$action': $msg", __FILE__);
  907. throw new ClientException($msg, $code);
  908. }
  909. /**
  910. * Returns the current URL
  911. *
  912. * @return string current URL
  913. */
  914. function selfUrl()
  915. {
  916. $action = $this->trimmed('action');
  917. $args = $this->args;
  918. unset($args['action']);
  919. if (common_config('site', 'fancy')) {
  920. unset($args['p']);
  921. }
  922. if (array_key_exists('submit', $args)) {
  923. unset($args['submit']);
  924. }
  925. foreach (array_keys($_COOKIE) as $cookie) {
  926. unset($args[$cookie]);
  927. }
  928. return common_local_url($action, $args);
  929. }
  930. /**
  931. * Generate a menu item
  932. *
  933. * @param string $url menu URL
  934. * @param string $text menu name
  935. * @param string $title title attribute, null by default
  936. * @param boolean $is_selected current menu item, false by default
  937. * @param string $id element id, null by default
  938. *
  939. * @return nothing
  940. */
  941. function menuItem($url, $text, $title=null, $is_selected=false, $id=null)
  942. {
  943. // Added @id to li for some control.
  944. // XXX: We might want to move this to htmloutputter.php
  945. $lattrs = array();
  946. if ($is_selected) {
  947. $lattrs['class'] = 'current';
  948. }
  949. (is_null($id)) ? $lattrs : $lattrs['id'] = $id;
  950. $this->elementStart('li', $lattrs);
  951. $attrs['href'] = $url;
  952. if ($title) {
  953. $attrs['title'] = $title;
  954. }
  955. $this->element('a', $attrs, $text);
  956. $this->elementEnd('li');
  957. }
  958. /**
  959. * Generate pagination links
  960. *
  961. * @param boolean $have_before is there something before?
  962. * @param boolean $have_after is there something after?
  963. * @param integer $page current page
  964. * @param string $action current action
  965. * @param array $args rest of query arguments
  966. *
  967. * @return nothing
  968. */
  969. function pagination($have_before, $have_after, $page, $action, $args=null)
  970. {
  971. // Does a little before-after block for next/prev page
  972. if ($have_before || $have_after) {
  973. $this->elementStart('div', array('class' => 'pagination'));
  974. $this->elementStart('dl', null);
  975. $this->element('dt', null, _('Pagination'));
  976. $this->elementStart('dd', null);
  977. $this->elementStart('ul', array('class' => 'nav'));
  978. }
  979. if ($have_before) {
  980. $pargs = array('page' => $page-1);
  981. $this->elementStart('li', array('class' => 'nav_prev'));
  982. $this->element('a', array('href' => common_local_url($action, $args, $pargs),
  983. 'rel' => 'prev'),
  984. _('After'));
  985. $this->elementEnd('li');
  986. }
  987. if ($have_after) {
  988. $pargs = array('page' => $page+1);
  989. $this->elementStart('li', array('class' => 'nav_next'));
  990. $this->element('a', array('href' => common_local_url($action, $args, $pargs),
  991. 'rel' => 'next'),
  992. _('Before'));
  993. $this->elementEnd('li');
  994. }
  995. if ($have_before || $have_after) {
  996. $this->elementEnd('ul');
  997. $this->elementEnd('dd');
  998. $this->elementEnd('dl');
  999. $this->elementEnd('div');
  1000. }
  1001. }
  1002. /**
  1003. * An array of feeds for this action.
  1004. *
  1005. * Returns an array of potential feeds for this action.
  1006. *
  1007. * @return array Feed object to show in head and links
  1008. */
  1009. function getFeeds()
  1010. {
  1011. return null;
  1012. }
  1013. /**
  1014. * A design for this action
  1015. *
  1016. * @return Design a design object to use
  1017. */
  1018. function getDesign()
  1019. {
  1020. return Design::siteDesign();
  1021. }
  1022. }