PageRenderTime 34ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/system/cms/modules/pages/plugin.php

https://github.com/asalem/pyrocms
PHP | 696 lines | 461 code | 75 blank | 160 comment | 33 complexity | b204811b55c58f5ba204ebfae82e69fe MD5 | raw file
Possible License(s): CC-BY-3.0, BSD-3-Clause, CC0-1.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, MIT
  1. <?php
  2. use Pyro\Module\Pages\Model\Page;
  3. /**
  4. * Pages Plugin
  5. *
  6. * Create links and whatnot.
  7. *
  8. * @author PyroCMS Dev Team
  9. * @package PyroCMS\Core\Modules\Pages\Plugins
  10. */
  11. class Plugin_Pages extends Plugin
  12. {
  13. public $version = '1.0.0';
  14. public $name = array(
  15. 'en' => 'Pages',
  16. 'fa' => 'صفحه ها',
  17. );
  18. public $description = array(
  19. 'en' => 'Output page data or build a list of pages in a page tree.',
  20. 'fa'=> 'محتویات صفحه را نشان دهید و یا ساختار درختی صفحات را نمایش دهید'
  21. );
  22. /**
  23. * Returns a PluginDoc array that PyroCMS uses
  24. * to build the reference in the admin panel
  25. *
  26. * All options are listed here but refer
  27. * to the Blog plugin for a larger example
  28. *
  29. * @todo fill the array with details about this plugin, then uncomment the return value.
  30. *
  31. * @return array
  32. */
  33. public function _self_doc()
  34. {
  35. $info = array(
  36. 'url' => array(// the name of the method you are documenting
  37. 'description' => array(// a single sentence to explain the purpose of this method
  38. 'en' => 'Outputs the page url.'
  39. ),
  40. 'single' => true,
  41. 'double' => false,
  42. 'variables' => '',
  43. 'attributes' => array(
  44. 'id' => array(
  45. 'type' => 'number',
  46. 'flags' => '',
  47. 'default' => '',
  48. 'required' => true,
  49. ),
  50. ),
  51. ),// end url method
  52. 'display' => array(// the name of the method you are documenting
  53. 'description' => array(// a single sentence to explain the purpose of this method
  54. 'en' => 'Output the data of any live page.'
  55. ),
  56. 'single' => true,
  57. 'double' => true,
  58. 'variables' => 'title|slug|uri|parent_id|type_id|entry_id|css|js|meta_title|meta_keywords|meta_description|rss_enabled|comments_enabled|status|created_at|updated_at|restricted_to|is_home|strict_uri|page_type_slug|page_type_title|custom_fields }}{{ field }}{{ /custom_fields',
  59. 'attributes' => array(
  60. 'id' => array(
  61. 'type' => 'number',
  62. 'flags' => '',
  63. 'default' => '',
  64. 'required' => false,
  65. ),
  66. 'slug' => array(
  67. 'type' => 'slug',
  68. 'flags' => '',
  69. 'default' => '',
  70. 'required' => false,
  71. ),
  72. ),
  73. ),// end display method
  74. 'children' => array(// the name of the method you are documenting
  75. 'description' => array(// a single sentence to explain the purpose of this method
  76. 'en' => 'Show the children of any page.'
  77. ),
  78. 'single' => false,
  79. 'double' => true,
  80. 'variables' => 'title|slug|uri|parent_id|type_id|entry_id|css|js|meta_title|meta_keywords|meta_description|rss_enabled|comments_enabled|status|created_at|updated_at|restricted_to|is_home|strict_uri|page_type_slug|page_type_title|custom_fields }}{{ field }}{{ /custom_fields',
  81. 'attributes' => array(
  82. 'id' => array(
  83. 'type' => 'number',
  84. 'flags' => '',
  85. 'default' => '',
  86. 'required' => false,
  87. ),
  88. 'slug' => array(
  89. 'type' => 'slug',
  90. 'flags' => '',
  91. 'default' => '',
  92. 'required' => false,
  93. ),
  94. 'order-by' => array(
  95. 'type' => 'flag',
  96. 'flags' => 'title|slug|uri|parent_id|status|created_at|updated_at|order|page_type_slug|page_type_title',
  97. 'default' => 'order',
  98. 'required' => false,
  99. ),
  100. 'order-dir' => array(
  101. 'type' => 'flag',
  102. 'flags' => 'asc|desc|random',
  103. 'default' => 'asc',
  104. 'required' => false,
  105. ),
  106. 'limit' => array(
  107. 'type' => 'number',
  108. 'flags' => '',
  109. 'default' => '',
  110. 'required' => false,
  111. ),
  112. 'offset' => array(
  113. 'type' => 'number',
  114. 'flags' => '',
  115. 'default' => '0',
  116. 'required' => false,
  117. ),
  118. 'include-types' => array(
  119. 'type' => 'number',
  120. 'flags' => '',
  121. 'default' => '',
  122. 'required' => false,
  123. ),
  124. ),
  125. ),// end children method
  126. 'page_tree' => array(// the name of the method you are documenting
  127. 'description' => array(// a single sentence to explain the purpose of this method
  128. 'en' => 'Create a tree of a page\'s children specified by the ID or URI.',
  129. ),
  130. 'single' => true,
  131. 'double' => false,
  132. 'variables' => '',
  133. 'attributes' => array(
  134. 'start-id' => array(
  135. 'type' => 'number',
  136. 'flags' => '',
  137. 'default' => '',
  138. 'required' => false,
  139. ),
  140. 'start' => array(
  141. 'type' => 'text',
  142. 'flags' => '',
  143. 'default' => '',
  144. 'required' => false,
  145. ),
  146. 'disable-levels' => array(
  147. 'type' => 'slug',
  148. 'flags' => '',
  149. 'default' => '',
  150. 'required' => false,
  151. ),
  152. 'order-by' => array(
  153. 'type' => 'flag',
  154. 'flags' => 'title|slug|uri|parent_id|status|created_at|updated_at|order|page_type_slug|page_type_title',
  155. 'default' => 'order',
  156. 'required' => false,
  157. ),
  158. 'order-dir' => array(
  159. 'type' => 'flag',
  160. 'flags' => 'asc|desc|random',
  161. 'default' => 'asc',
  162. 'required' => false,
  163. ),
  164. 'list-tag' => array(
  165. 'type' => 'text',
  166. 'flags' => '',
  167. 'default' => 'ul',
  168. 'required' => false,
  169. ),
  170. 'link' => array(
  171. 'type' => 'flag',
  172. 'flags' => 'Y|N',
  173. 'default' => 'Y',
  174. 'required' => false,
  175. ),
  176. ),
  177. ),// end page_tree method
  178. 'is' => array(// the name of the method you are documenting
  179. 'description' => array(// a single sentence to explain the purpose of this method
  180. 'en' => 'Check the relationship of a page to another by passing the IDs to check. [children] can take multiple IDs separated with |',
  181. ),
  182. 'single' => true,
  183. 'double' => false,
  184. 'variables' => '',
  185. 'attributes' => array(
  186. 'children' => array(
  187. 'type' => 'number',
  188. 'flags' => '',
  189. 'default' => '',
  190. 'required' => false,
  191. ),
  192. 'child' => array(
  193. 'type' => 'number',
  194. 'flags' => '',
  195. 'default' => '',
  196. 'required' => false,
  197. ),
  198. 'descendent' => array(
  199. 'type' => 'number',
  200. 'flags' => '',
  201. 'default' => '',
  202. 'required' => false,
  203. ),
  204. 'parent' => array(
  205. 'type' => 'number',
  206. 'flags' => '',
  207. 'default' => '',
  208. 'required' => false,
  209. ),
  210. ),
  211. ),// end is method
  212. 'has' => array(// the name of the method you are documenting
  213. 'description' => array(// a single sentence to explain the purpose of this method
  214. 'en' => 'Check if a page has a child with the specified ID',
  215. ),
  216. 'single' => true,
  217. 'double' => false,
  218. 'variables' => '',
  219. 'attributes' => array(
  220. 'id' => array(
  221. 'type' => 'number',
  222. 'flags' => '',
  223. 'default' => '',
  224. 'required' => true,
  225. ),
  226. ),
  227. ),// end has method
  228. );
  229. return $info;
  230. }
  231. /**
  232. * Get the URL of a page
  233. *
  234. * Attributes:
  235. * - (int) id : The id of the page to get the URL for.
  236. *
  237. * @return string
  238. */
  239. public function url()
  240. {
  241. $id = $this->attribute('id');
  242. $page = Page::find($id);
  243. return site_url($page ? $page->uri : '');
  244. }
  245. /**
  246. * Get a page by ID or slug
  247. *
  248. * Attributes:
  249. * - (int) id: The id of the page.
  250. *
  251. * @return array
  252. */
  253. public function display()
  254. {
  255. $page = Page::findByIdAndStatus($this->attribute('id'), 'live');
  256. // Check for custom fields
  257. if (is_scalar($this->content()) and strpos($this->content(), 'custom_fields') !== false and $page) {
  258. $custom_fields = array();
  259. $this->load->driver('Streams');
  260. $stream = $this->streams_m->get_stream($page->stream_id);
  261. $params = array(
  262. 'stream' => $stream->stream_slug,
  263. 'namespace' => $stream->stream_namespace,
  264. 'include' => $page->entry_id,
  265. 'disable' => 'created_by'
  266. );
  267. $entries = $this->streams->entries->get_entries($params);
  268. foreach ($entries['entries'] as $entry) {
  269. $custom_fields[$page->stream_id][$entry['id']] = $entry;
  270. }
  271. } else {
  272. $custom_fields = false;
  273. }
  274. // If we have custom fields, we need to roll our
  275. // entry values in.
  276. if ($custom_fields and isset($custom_fields[$page->stream_id][$page->entry_id])) {
  277. $page->custom_fields = array($custom_fields[$page->stream_id][$page->entry_id]);
  278. }
  279. return $this->content() ? array($page) : $page->body;
  280. }
  281. /**
  282. * Children list
  283. *
  284. * Creates a list of child pages one level under the parent page.
  285. *
  286. * Attributes:
  287. * - (int) limit: How many pages to show.
  288. * - (string) order-by: One of the column names from the `pages` table.
  289. * - (string) order-dir: Either `asc` or `desc`
  290. *
  291. * Usage:
  292. * {{ pages:children id="1" limit="5" }}
  293. * <h2>{{title}}</h2>
  294. * {{body}}
  295. * {{ /pages:children }}
  296. *
  297. * @return array
  298. */
  299. public function children()
  300. {
  301. $limit = $this->attribute('limit', null);
  302. $offset = $this->attribute('offset');
  303. $order_by = $this->attribute('order-by', 'order');
  304. $order_dir = $this->attribute('order-dir', 'ASC');
  305. $page_types = $this->attribute('include-types', $this->attribute('include_types'));
  306. // Restrict page types.
  307. // Page types can be provided in a pipe (|) delimited string.
  308. // Ex: 4|6
  309. if ($page_types) {
  310. $types = explode('|', $page_types);
  311. foreach ($types as $type) {
  312. $this->db->where('pages.type_id', $type);
  313. }
  314. }
  315. $pages = $this->db->select('pages.*, page_types.stream_id, page_types.slug as page_type_slug, page_types.title as page_type_title')
  316. ->where('pages.parent_id', $this->attribute('id'))
  317. ->where('status', 'live')
  318. ->join('page_types', 'page_types.id = pages.type_id', 'left')
  319. ->order_by($order_by, $order_dir)
  320. ->limit($limit)
  321. ->offset($offset)
  322. ->get('pages')
  323. ->result_array();
  324. // Custom fields provision.
  325. // Since page children can have many different page types,
  326. // we are going to do this in the most economical way possible:
  327. // Find the entries by ID and attach them to a special custom_fields
  328. // variable.
  329. if (is_scalar($this->content()) and strpos($this->content(), 'custom_fields') !== false and $pages) {
  330. $custom_fields = array();
  331. $this->load->driver('Streams');
  332. // Get all of the IDs by page type id.
  333. // Ex: array('page_type_id' => array('1', '2'))
  334. $entries = array();
  335. foreach ($pages as $page) {
  336. $entries[$page['stream_id']][] = $page['entry_id'];
  337. }
  338. // Get our entries by steram
  339. foreach ($entries as $stream_id => $entry_ids) {
  340. $stream = $this->streams_m->get_stream($stream_id);
  341. $params = array(
  342. 'stream' => $stream->stream_slug,
  343. 'namespace' => $stream->stream_namespace,
  344. 'include' => implode('|', $entry_ids),
  345. 'disable' => 'created_by'
  346. );
  347. $entries = $this->streams->entries->get_entries($params);
  348. // Set the entries in our custom fields array for
  349. // easy reference later when processing our pages.
  350. foreach ($entries['entries'] as $entry) {
  351. $custom_fields[$stream_id][$entry['id']] = $entry;
  352. }
  353. }
  354. } else {
  355. $custom_fields = false;
  356. }
  357. // Legacy support for chunks
  358. if ($pages and $this->db->table_exists('page_chunks')) {
  359. foreach ($pages as &$page) {
  360. // Grab all the chunks that make up the body for this page
  361. $page['chunks'] = $this->db
  362. ->get_where('page_chunks', array('page_id' => $page['id']))
  363. ->result_array();
  364. $page['body'] = '';
  365. if ($page['chunks']) {
  366. foreach ($page['chunks'] as $chunk) {
  367. $page['body'] .= '<div class="page-chunk ' . $chunk['slug'] . '">' .
  368. (($chunk['type'] == 'markdown') ? $chunk['parsed'] : $chunk['body']) .
  369. '</div>' . PHP_EOL;
  370. }
  371. }
  372. }
  373. }
  374. // Process our pages.
  375. foreach ($pages as &$page) {
  376. // First, let's get a full URL. This is just
  377. // handy to have around.
  378. $page['url'] = site_url($page['uri']);
  379. // Now let's process our keywords hash.
  380. $keywords = Keywords::get($page['meta_keywords']);
  381. // In order to properly display the keywords in Lex
  382. // tags, we need to format them.
  383. $formatted_keywords = array();
  384. foreach ($keywords as $key) {
  385. $formatted_keywords[] = array('keyword' => $key->name);
  386. }
  387. $page['meta_keywords'] = $formatted_keywords;
  388. // If we have custom fields, we need to roll our
  389. // entry values in.
  390. if ($custom_fields and isset($custom_fields[$page['stream_id']][$page['entry_id']])) {
  391. $page['custom_fields'] = array($custom_fields[$page['stream_id']][$page['entry_id']]);
  392. }
  393. }
  394. //return '<pre>'.print_r($pages, true).'</pre>';
  395. return $pages;
  396. }
  397. /**
  398. * Page tree function
  399. *
  400. * Creates a nested ul of child pages
  401. *
  402. * Usage:
  403. * {{ pages:page_tree start-id="5" }}
  404. * optional attributes:
  405. *
  406. * disable-levels="slug"
  407. * order-by="title"
  408. * order-dir="desc"
  409. * list-tag="ul"
  410. * link="true"
  411. *
  412. * @param array
  413. * @return array
  414. */
  415. public function page_tree()
  416. {
  417. $start = $this->attribute('start');
  418. $start_id = $this->attribute('start-id', $this->attribute('start_id'));
  419. $disable_levels = $this->attribute('disable-levels');
  420. $order_by = $this->attribute('order-by', 'order');
  421. $order_dir = $this->attribute('order-dir', 'ASC');
  422. $list_tag = $this->attribute('list-tag', 'ul');
  423. $link = $this->attribute('link', true);
  424. // If we have a start URI, let's try and
  425. // find that ID.
  426. if ($start) {
  427. $page = Page::findByUri($start);
  428. if (! $page) {
  429. return null;
  430. }
  431. $start_id = $page->id;
  432. }
  433. // If our start doesn't exist, then
  434. // what are we going to do? Nothing.
  435. if (! $start_id) {
  436. return null;
  437. }
  438. // Disable individual pages or parent pages by submitting their slug
  439. $this->disable = explode("|", $disable_levels);
  440. return '<'.$list_tag.'>'.$this->buildTreeHtml(array(
  441. 'parent_id' => $start_id,
  442. 'order_by' => $order_by,
  443. 'order_dir' => $order_dir,
  444. 'list_tag' => $list_tag,
  445. 'link' => $link
  446. )).'</'.$list_tag.'>';
  447. }
  448. /**
  449. * Page is function
  450. *
  451. * Check the pages parent or descendent relation
  452. *
  453. * Usage:
  454. * {{ pages:is child="7" parent="cookbook" }} // return 1 (true)
  455. * {{ pages:is child="recipes" descendent="books" }} // return 1 (true)
  456. * {{ pages:is children="7,8,literature" parent="6" }} // return 0 (false)
  457. * {{ pages:is children="recipes,ingredients,9" descendent="4" }} // return 1 (true)
  458. *
  459. * Use Id or Slug as param, following usage data reference
  460. * Id: 4 = Slug: books
  461. * Id: 6 = Slug: cookbook
  462. * Id: 7 = Slug: recipes
  463. * Id: 8 = Slug: ingredients
  464. * Id: 9 = Slug: literature
  465. *
  466. * @param array Plugin attributes
  467. * @return int 0 or 1
  468. */
  469. public function is()
  470. {
  471. $children_ids = $this->attribute('children');
  472. $child_id = $this->attribute('child');
  473. if (! $children_ids) {
  474. return (int) $this->_check_page_is($child_id);
  475. }
  476. $children_ids = explode('|', str_replace(',', '|', $children_ids));
  477. $children_ids = array_map('trim', $children_ids);
  478. if ($child_id) {
  479. $children_ids[] = $child_id;
  480. }
  481. foreach ($children_ids as $child_id) {
  482. if ( ! $this->_check_page_is($child_id)) {
  483. return (int) false;
  484. }
  485. }
  486. return (int) true;
  487. }
  488. /**
  489. * Page has function
  490. *
  491. * Check if this page has children
  492. *
  493. * Usage:
  494. * {{ pages:has id="4" }}
  495. *
  496. * @param int id The id of the page you want to check
  497. * @return bool
  498. */
  499. public function has()
  500. {
  501. return $this->page_m->has_children($this->attribute('id'));
  502. }
  503. /**
  504. * Check Page is function
  505. *
  506. * Works for page is function
  507. *
  508. * @param mixed The Id or Slug of the page
  509. * @param array Plugin attributes
  510. * @return int 0 or 1
  511. */
  512. private function _check_page_is($child_id = 0)
  513. {
  514. $descendent_id = $this->attribute('descendent');
  515. $parent_id = $this->attribute('parent');
  516. if ($child_id and $descendent_id) {
  517. if ( ! is_numeric($child_id)) {
  518. $child_id = ($child = $this->page_m->get_by(array('slug' => $child_id))) ? $child->id : 0;
  519. }
  520. if ( ! is_numeric($descendent_id)) {
  521. $descendent_id = ($descendent = $this->page_m->get_by(array('slug' => $descendent_id))) ? $descendent->id : 0;
  522. }
  523. if ( ! ($child_id and $descendent_id)) {
  524. return false;
  525. }
  526. $descendent_ids = $this->page_m->getDescendantIds($descendent_id);
  527. return in_array($child_id, $descendent_ids);
  528. }
  529. if ($child_id and $parent_id) {
  530. if ( ! is_numeric($child_id)) {
  531. $parent_id = ($parent = $this->page_m->get_by(array('slug' => $parent_id))) ? $parent->id : 0;
  532. }
  533. return $parent_id ? (int) $this->page_m->count_by(array(
  534. (is_numeric($child_id) ? 'id' : 'slug') => $child_id,
  535. 'parent_id' => $parent_id
  536. )) > 0 : false;
  537. }
  538. }
  539. /**
  540. * Tree html function
  541. *
  542. * Creates a page tree
  543. *
  544. * @param array
  545. * @return array
  546. */
  547. private function buildTreeHtml($params)
  548. {
  549. $params = array_merge(array(
  550. 'tree' => array(),
  551. 'parent_id' => 0
  552. ), $params);
  553. extract($params);
  554. if (! $tree) {
  555. $this->db
  556. ->select('id, parent_id, slug, uri, title')
  557. ->where_not_in('slug', $this->disable);
  558. // check if they're logged in
  559. if ( isset($this->current_user->group) ) {
  560. // admins can see them all
  561. if ($this->current_user->group != 'admin') {
  562. $id_list = array();
  563. $page_list = $this->db
  564. ->select('id, restricted_to')
  565. ->get('pages')
  566. ->result();
  567. foreach ($page_list as $list_item) {
  568. // make an array of allowed user groups
  569. $group_array = explode(',', $list_item->restricted_to);
  570. // if restricted_to is 0 or empty (unrestricted) or if the current user's group is allowed
  571. if ( ($group_array[0] < 1) or in_array($this->current_user->group_id, $group_array) ) {
  572. $id_list[] = $list_item->id;
  573. }
  574. }
  575. // if it's an empty array then evidently all pages are unrestricted
  576. if ( count($id_list) > 0 ) {
  577. // select only the pages they have permissions for
  578. $this->db->where_in('id', $id_list);
  579. }
  580. // since they aren't an admin they can't see drafts
  581. $this->db->where('status', 'live');
  582. }
  583. } else {
  584. //they aren't logged in, show them all live, unrestricted pages
  585. $this->db
  586. ->where('status', 'live')
  587. ->where('restricted_to <', 1)
  588. ->or_where('restricted_to', null);
  589. }
  590. $pages = $this->db
  591. ->order_by($order_by, $order_dir)
  592. ->get('pages')
  593. ->result();
  594. if ($pages) {
  595. foreach ($pages as $page) {
  596. $tree[$page->parent_id][] = $page;
  597. }
  598. }
  599. }
  600. if ( ! isset($tree[$parent_id])) {
  601. return;
  602. }
  603. $html = '';
  604. foreach ($tree[$parent_id] as $item) {
  605. $html .= '<li';
  606. $html .= (current_url() == site_url($item->uri)) ? ' class="current">' : '>';
  607. $html .= ($link === true) ? '<a href="'.site_url($item->uri).'">'.$item->title.'</a>' : $item->title;
  608. $nested_list = $this->buildTreeHtml(array(
  609. 'tree' => $tree,
  610. 'parent_id' => (int) $item->id,
  611. 'link' => $link,
  612. 'list_tag' => $list_tag
  613. ));
  614. if ($nested_list) {
  615. $html .= '<'.$list_tag.'>'.$nested_list.'</'.$list_tag.'>';
  616. }
  617. $html .= '</li>';
  618. }
  619. return $html;
  620. }
  621. }