1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8function wikiplugin_include_info()
9{
10	return [
11		'name' => tr('Include'),
12		'documentation' => 'PluginInclude',
13		'description' => tr('Include a portion of another wiki page'),
14		'prefs' => ['wikiplugin_include'],
15		'iconname' => 'copy',
16		'introduced' => 1,
17		'tags' => [ 'basic' ],
18		'format' => 'wiki',
19		'params' => [
20			'page' => [
21				'required' => true,
22				'name' => tr('Page'),
23				'description' => tr('Name of the source wiki page (which contains the included portion)'),
24				'since' => '1',
25				'filter' => 'pagename',
26				'default' => '',
27				'profile_reference' => 'wiki_page',
28			],
29			'start' => [
30				'required' => false,
31				'name' => tr('Start'),
32				'description' => tr('When only a portion of the page should be included, full text of the line after which
33					inclusion should start'),
34				'since' => '1',
35				'default' => '',
36			],
37			'stop' => [
38				'required' => false,
39				'name' => tr('Stop'),
40				'description' => tr('When only a portion of the page should be included, full text of the line before which
41					inclusion should end'),
42				'since' => '1',
43				'default' => '',
44			],
45			'linkoriginal' => [
46				'required' => false,
47				'name' => tr('Read more button'),
48				'description' => tr('Add a "Read more" link at the end of included content, linking to the original page. (shows "Read More" by default)'),
49				'since' => '18.0',
50				'default' => 'n',
51				'options' => [
52					['text' => '', 'value' => ''],
53					['text' => tr('Yes'), 'value' => 'y'],
54					['text' => tr('No'), 'value' => 'n'],
55				],
56			],
57			'linkoriginal_text' => [
58				'required' => false,
59				'name' => tr('Read more button label'),
60				'description' => tr('Label of the button linking to the source page (if it is displayed)'),
61				'since' => '18.0',
62				'filter' => 'text',
63				'default' => '',
64			],
65			'nopage_text' => [
66				'required' => false,
67				'name' => tr('Page not found'),
68				'description' => tr('Text to show when the page selected to be included is not found anymore.'),
69				'since' => '6.0',
70				'filter' => 'text',
71				'default' => '',
72			],
73			'pagedenied_text' => [
74				'required' => false,
75				'name' => tr('Permission denied message'),
76				'description' => tr('Text to show when the page exists but the user has insufficient permissions to see it.'),
77				'since' => '6.0',
78				'filter' => 'text',
79				'default' => '',
80			],
81			'pagenotapproved_text' => [
82				'required' => false,
83				'name' => tr('No version approved error message'),
84				'description' => tr('Text to show when the page exists but no version is approved.'),
85				'since' => '18.0',
86				'filter' => 'text',
87				'default' => '',
88			],
89			'page_edit_icon' => [
90				'required' => false,
91				'name' => tr('Edit Icon'),
92				'description' => tr('Option to show the edit icon for the included page (shown by default). Depends on the "edit icons" settings.'),
93				'since' => '12.1',
94				'default' => 'y',
95				'filter' => 'alpha',
96				'options' => [
97					['text' => '', 'value' => ''],
98					['text' => tr('Yes'), 'value' => 'y'],
99					['text' => tr('No'), 'value' => 'n'],
100				],
101			],
102			'max_inclusions' => [
103				'required' => false,
104				'name' => tr('Max inclusions'),
105				'description' => tr('Maximum amount of times the same page can be included. Defaults to 5'),
106				'since' => '18.2',
107				'filter' => 'text',
108				'default' => 5,
109			],
110			'parse_included_page' => [
111				'required' => false,
112				'name' => tr('Parse Included Page'),
113				'description' => tr('Parse the page to be included before adding it to the parent page. This will help if html pages are included in wiki pages or vice versa, but will cause issues with the wiki table of contents.'),
114				'since' => '18.2',
115				'default' => 'n',
116				'filter' => 'alpha',
117				'options' => [
118					['text' => '', 'value' => ''],
119					['text' => tr('Yes'), 'value' => 'y'],
120					['text' => tr('No'), 'value' => 'n'],
121				],
122			],
123			'max_chars_included' => [
124				'required' => false,
125				'name' => tr('Max characters included'),
126				'description' => tr('Limit the length of the included text'),
127				'since' => '20.0',
128				'filter' => 'int',
129				'default' => '',
130			],
131		],
132	];
133}
134
135function wikiplugin_include($dataIn, $params)
136{
137	global $killtoc, $prefs;
138
139	/** @var int[] $numberOfInclusions Associative array of the number of times each key (fragment) was included */
140	static $numberOfInclusions;
141
142	static $data;
143	$tikilib = TikiLib::lib('tiki');
144
145	$killtoc = true;
146	$params = array_merge([
147		'nopage_text' => '',
148		'pagedenied_text' => '',
149		'page_edit_icon' => 'y',
150		'parse_included_page' => 'n',
151		'pagenotapproved_text' => tr('There are no approved versions of this page.'),
152	], $params);
153	extract($params, EXTR_SKIP);
154	if (! isset($page)) {
155		return ("<b>missing page for plugin INCLUDE</b><br />");
156	}
157	if (! isset($max_inclusions)) {
158		$max_inclusions = 5;
159	}
160
161	// This variable is for accessing included page name within plugins in that page
162	global $wikiplugin_included_page;
163	$wikiplugin_included_page = $page;
164
165	/** @var string $fragmentIdentifier Identifier of included fragment */
166	$fragmentIdentifier = $page;
167	if (isset($start)) {
168		$fragmentIdentifier .= "/$start";
169	}
170	if (isset($end)) {
171		$fragmentIdentifier .= "/$end";
172	}
173
174	if (isset($numberOfInclusions[$fragmentIdentifier])) {
175		if ($numberOfInclusions[$fragmentIdentifier] >= $max_inclusions) {
176			trigger_error('Inclusion failed', E_USER_WARNING);
177			return '';
178		}
179		$numberOfInclusions[$fragmentIdentifier]++;
180	} else {
181		$numberOfInclusions[$fragmentIdentifier] = 1;
182		// only evaluate permission the first time round
183		// evaluate if object or system permissions enables user to see the included page
184		if ($prefs['flaggedrev_approval'] != 'y') {
185			$data[$fragmentIdentifier] = $tikilib->get_page_info($page);
186		} else {
187			$flaggedrevisionlib = TikiLib::lib('flaggedrevision');
188			if ($flaggedrevisionlib->page_requires_approval($page)) {
189				if ($version_info = $flaggedrevisionlib->get_version_with($page, 'moderation', 'OK')) {
190					$data[$fragmentIdentifier] = $version_info;
191				} else {
192					$numberOfInclusions[$fragmentIdentifier] = $max_inclusions;
193					return($pagenotapproved_text);
194				}
195			} else {
196				$data[$fragmentIdentifier] = $tikilib->get_page_info($page);
197			}
198		}
199		if (! $data[$fragmentIdentifier]) {
200			$text = $nopage_text;
201		}
202		$perms = $tikilib->get_perm_object($page, 'wiki page', $data[$fragmentIdentifier], false);
203		if ($perms['tiki_p_view'] != 'y') {
204			$numberOfInclusions[$fragmentIdentifier] = $max_inclusions;
205			$text = $pagedenied_text;
206			return($text);
207		}
208	}
209
210	if (! empty($params['linkoriginal_text'])) {
211		$linkoriginal_text = tr($params['linkoriginal_text']);
212	} else {
213		$linkoriginal_text = tr('Read more');
214	}
215
216	if ($data[$fragmentIdentifier]) {
217		$text = $data[$fragmentIdentifier]['data'];
218		if (isset($start) || isset($stop)) {
219			$lines = explode("\n", $text);
220			if (isset($start) && isset($stop)) {
221				$state = 0;
222				foreach ($lines as $i => $line) {
223					if ($state == 0) {
224						// Searching for start marker, dropping lines until found
225						unset($lines[$i]);	// Drop the line
226						if (0 == strcmp($start, trim($line))) {
227							$state = 1;	// Start retaining lines and searching for stop marker
228						}
229					} else {
230						// Searching for stop marker, retaining lines until found
231						if (0 == strcmp($stop, trim($line))) {
232							unset($lines[$i]);	// Stop marker, drop the line
233							$state = 0; 		// Go back to looking for start marker
234						}
235					}
236				}
237			} elseif (isset($start)) {
238				// Only start marker is set. Search for it, dropping all lines until
239				// it is found.
240				foreach ($lines as $i => $line) {
241					unset($lines[$i]); // Drop the line
242					if (0 == strcmp($start, trim($line))) {
243						break;
244					}
245				}
246			} else {
247				// Only stop marker is set. Search for it, dropping all lines after
248				// it is found.
249				$state = 1;
250				foreach ($lines as $i => $line) {
251					if ($state == 0) {
252						// Dropping lines
253						unset($lines[$i]);
254					} else {
255						// Searching for stop marker, retaining lines until found
256						if (0 == strcmp($stop, trim($line))) {
257							unset($lines[$i]);	// Stop marker, drop the line
258							$state = 0; 		// Start dropping lines
259						}
260					}
261				}
262			}
263			$text = implode("\n", $lines);
264		}
265	}
266
267	if (isset($params['max_chars_included']) && mb_strlen($text) > $params['max_chars_included']) {
268		$text = substr($text, 0 , $params['max_chars_included']) . '…';
269	}
270
271	$parserlib = TikiLib::lib('parser');
272
273	if ($params['parse_included_page'] === 'y') {
274
275		$old_options = $parserlib->option;
276		$options = [
277			'is_html' => $data[$fragmentIdentifier]['is_html'],
278			'suppress_icons' => true,
279		];
280		if (! empty($_REQUEST['page'])) {
281			$options['page'] = $_REQUEST['page'];
282		}
283		$parserlib->setOptions($options);
284		$fragment = new WikiParser_Parsable($text);
285		$text = $fragment->parse($options);
286		$parserlib->setOptions($old_options);
287	} else {
288		if (!empty($_REQUEST['page'])) {
289			$options['page'] = $_REQUEST['page'];
290		}
291		$parserlib->setOptions();
292		$parserlib->parse_wiki_argvariable($text);
293	}
294
295	global $smarty;
296	// append a "See full page" link at end of text if only a portion of page is being included
297	if (($prefs['wiki_plugin_include_link_original'] == 'y' && (isset($start) || isset($stop))) || (isset($linkoriginal) && $linkoriginal == 'y')) {
298		$wikilib = TikiLib::lib('wiki');
299		$text .= '<p><a href="' . $wikilib->sefurl($page) . '" class="btn btn-primary"';
300		$text .= 'title="' . sprintf(tr('The text above comes from page "%s". Click to go to that page.'), htmlspecialchars($page)) . '">';
301		$text .= smarty_function_icon(['name' => 'align-left'], $smarty->getEmptyInternalTemplate()) . ' ';
302		$text .= $linkoriginal_text;
303		$text .= '</a><p>';
304	}
305
306	// append an edit button if page_edit_icon does not equal 'n'
307	if ($page_edit_icon != 'n') {
308		if (isset($perms) && $perms['tiki_p_edit'] === 'y' && strpos($_SERVER['PHP_SELF'], 'tiki-send_newsletters.php') === false) {
309			$smarty = TikiLib::lib('smarty');
310			$smarty->loadPlugin('smarty_block_ajax_href');
311			$smarty->loadPlugin('smarty_function_icon');
312			$tip = tr('Include Plugin') . ' | ' . tr('Edit the included page:') . ' &quot;' . $page . '&quot;';
313			$returnto = ! empty($GLOBALS['page']) ? $GLOBALS['page'] : $_SERVER['REQUEST_URI'];
314			if (empty($_REQUEST['display']) || $_REQUEST['display'] != 'pdf') {
315				$text .= '<a class="editplugin" ' . // ironically smarty_block_self_link doesn't work for this! ;)
316				smarty_block_ajax_href(['template' => 'tiki-editpage.tpl'], 'tiki-editpage.php?page=' . urlencode($page) . '&returnto=' . urlencode($returnto), $smarty, $tmp = false) . '>' .
317				smarty_function_icon(['name' => 'edit', 'iclass' => 'tips', 'ititle' => $tip], $smarty->getEmptyInternalTemplate()) . '</a>';
318			}
319		}
320	}
321	if ($params['parse_included_page'] === 'y') {
322		return "~np~$text~/np~";
323	} else {
324		return $text;
325	}
326}
327