1<?php
2/**
3 * This file is part of RSS-Bridge, a PHP project capable of generating RSS and
4 * Atom feeds for websites that don't have one.
5 *
6 * For the full license information, please view the UNLICENSE file distributed
7 * with this source code.
8 *
9 * @package	Core
10 * @license	http://unlicense.org/ UNLICENSE
11 * @link	https://github.com/rss-bridge/rss-bridge
12 */
13
14/**
15 * A generator class for a single bridge card on the home page of RSS-Bridge.
16 *
17 * This class generates the HTML content for a single bridge card for the home
18 * page of RSS-Bridge.
19 *
20 * @todo Return error if a caller creates an object of this class.
21 */
22final class BridgeCard {
23	/**
24	 * Build a HTML document string of buttons for each of the provided formats
25	 *
26	 * @param array $formats A list of format names
27	 * @return string The document string
28	 */
29	private static function buildFormatButtons($formats) {
30		$buttons = '';
31
32		foreach($formats as $name) {
33			$buttons .= '<button type="submit" name="format" value="'
34			. $name
35			. '">'
36			. $name
37			. '</button>'
38			. PHP_EOL;
39		}
40
41		return $buttons;
42	}
43
44	/**
45	 * Get the form header for a bridge card
46	 *
47	 * @param string $bridgeName The bridge name
48	 * @param bool $isHttps If disabled, adds a warning to the form
49	 * @return string The form header
50	 */
51	private static function getFormHeader($bridgeName, $isHttps = false, $parameterName = '') {
52		$form = <<<EOD
53			<form method="GET" action="?">
54				<input type="hidden" name="action" value="display" />
55				<input type="hidden" name="bridge" value="{$bridgeName}" />
56EOD;
57
58		if(!empty($parameterName)) {
59			$form .= <<<EOD
60				<input type="hidden" name="context" value="{$parameterName}" />
61EOD;
62		}
63
64		if(!$isHttps) {
65			$form .= '<div class="secure-warning">Warning :
66This bridge is not fetching its content through a secure connection</div>';
67		}
68
69		return $form;
70	}
71
72	/**
73	 * Get the form body for a bridge
74	 *
75	 * @param string $bridgeName The bridge name
76	 * @param array $formats A list of supported formats
77	 * @param bool $isActive Indicates if a bridge is enabled or not
78	 * @param bool $isHttps Indicates if a bridge uses HTTPS or not
79	 * @param string $parameterName Sets the bridge context for the current form
80	 * @param array $parameters The bridge parameters
81	 * @return string The form body
82	 */
83	private static function getForm($bridgeName,
84	$formats,
85	$isActive = false,
86	$isHttps = false,
87	$parameterName = '',
88	$parameters = array()) {
89		$form = self::getFormHeader($bridgeName, $isHttps, $parameterName);
90
91		if(count($parameters) > 0) {
92
93			$form .= '<div class="parameters">';
94
95			foreach($parameters as $id => $inputEntry) {
96				if(!isset($inputEntry['exampleValue']))
97					$inputEntry['exampleValue'] = '';
98
99				if(!isset($inputEntry['defaultValue']))
100					$inputEntry['defaultValue'] = '';
101
102				$idArg = 'arg-'
103					. urlencode($bridgeName)
104					. '-'
105					. urlencode($parameterName)
106					. '-'
107					. urlencode($id);
108
109				$form .= '<label for="'
110					. $idArg
111					. '">'
112					. filter_var($inputEntry['name'], FILTER_SANITIZE_STRING)
113					. '</label>'
114					. PHP_EOL;
115
116				if(!isset($inputEntry['type']) || $inputEntry['type'] === 'text') {
117					$form .= self::getTextInput($inputEntry, $idArg, $id);
118				} elseif($inputEntry['type'] === 'number') {
119					$form .= self::getNumberInput($inputEntry, $idArg, $id);
120				} else if($inputEntry['type'] === 'list') {
121					$form .= self::getListInput($inputEntry, $idArg, $id);
122				} elseif($inputEntry['type'] === 'checkbox') {
123					$form .= self::getCheckboxInput($inputEntry, $idArg, $id);
124				}
125
126				if(isset($inputEntry['title']))
127					$form .= '<i class="info" title="' . filter_var($inputEntry['title'], FILTER_SANITIZE_STRING) . '">i</i>';
128				else
129					$form .= '<i class="no-info"></i>';
130			}
131
132			$form .= '</div>';
133
134		}
135
136		if($isActive) {
137			$form .= self::buildFormatButtons($formats);
138		} else {
139			$form .= '<span style="font-weight: bold;">Inactive</span>';
140		}
141
142		return $form . '</form>' . PHP_EOL;
143	}
144
145	/**
146	 * Get input field attributes
147	 *
148	 * @param array $entry The current entry
149	 * @return string The input field attributes
150	 */
151	private static function getInputAttributes($entry) {
152		$retVal = '';
153
154		if(isset($entry['required']) && $entry['required'] === true)
155			$retVal .= ' required';
156
157		if(isset($entry['pattern']))
158			$retVal .= ' pattern="' . $entry['pattern'] . '"';
159
160		return $retVal;
161	}
162
163	/**
164	 * Get text input
165	 *
166	 * @param array $entry The current entry
167	 * @param string $id The field ID
168	 * @param string $name The field name
169	 * @return string The text input field
170	 */
171	private static function getTextInput($entry, $id, $name) {
172		return '<input '
173		. self::getInputAttributes($entry)
174		. ' id="'
175		. $id
176		. '" type="text" value="'
177		. filter_var($entry['defaultValue'], FILTER_SANITIZE_STRING)
178		. '" placeholder="'
179		. filter_var($entry['exampleValue'], FILTER_SANITIZE_STRING)
180		. '" name="'
181		. $name
182		. '" />'
183		. PHP_EOL;
184	}
185
186	/**
187	 * Get number input
188	 *
189	 * @param array $entry The current entry
190	 * @param string $id The field ID
191	 * @param string $name The field name
192	 * @return string The number input field
193	 */
194	private static function getNumberInput($entry, $id, $name) {
195		return '<input '
196		. self::getInputAttributes($entry)
197		. ' id="'
198		. $id
199		. '" type="number" value="'
200		. filter_var($entry['defaultValue'], FILTER_SANITIZE_NUMBER_INT)
201		. '" placeholder="'
202		. filter_var($entry['exampleValue'], FILTER_SANITIZE_NUMBER_INT)
203		. '" name="'
204		. $name
205		. '" />'
206		. PHP_EOL;
207	}
208
209	/**
210	 * Get list input
211	 *
212	 * @param array $entry The current entry
213	 * @param string $id The field ID
214	 * @param string $name The field name
215	 * @return string The list input field
216	 */
217	private static function getListInput($entry, $id, $name) {
218		if(isset($entry['required']) && $entry['required'] === true) {
219			Debug::log('The "required" attribute is not supported for lists.');
220			unset($entry['required']);
221		}
222
223		$list = '<select '
224		. self::getInputAttributes($entry)
225		. ' id="'
226		. $id
227		. '" name="'
228		. $name
229		. '" >';
230
231		foreach($entry['values'] as $name => $value) {
232			if(is_array($value)) {
233				$list .= '<optgroup label="' . htmlentities($name) . '">';
234				foreach($value as $subname => $subvalue) {
235					if($entry['defaultValue'] === $subname
236						|| $entry['defaultValue'] === $subvalue) {
237						$list .= '<option value="'
238							. $subvalue
239							. '" selected>'
240							. $subname
241							. '</option>';
242					} else {
243						$list .= '<option value="'
244							. $subvalue
245							. '">'
246							. $subname
247							. '</option>';
248					}
249				}
250				$list .= '</optgroup>';
251			} else {
252				if($entry['defaultValue'] === $name
253					|| $entry['defaultValue'] === $value) {
254					$list .= '<option value="'
255						. $value
256						. '" selected>'
257						. $name
258						. '</option>';
259				} else {
260					$list .= '<option value="'
261						. $value
262						. '">'
263						. $name
264						. '</option>';
265				}
266			}
267		}
268
269		$list .= '</select>';
270
271		return $list;
272	}
273
274	/**
275	 * Get checkbox input
276	 *
277	 * @param array $entry The current entry
278	 * @param string $id The field ID
279	 * @param string $name The field name
280	 * @return string The checkbox input field
281	 */
282	private static function getCheckboxInput($entry, $id, $name) {
283		if(isset($entry['required']) && $entry['required'] === true) {
284			Debug::log('The "required" attribute is not supported for checkboxes.');
285			unset($entry['required']);
286		}
287
288		return '<input '
289		. self::getInputAttributes($entry)
290		. ' id="'
291		. $id
292		. '" type="checkbox" name="'
293		. $name
294		. '" '
295		. ($entry['defaultValue'] === 'checked' ? 'checked' : '')
296		. ' />'
297		. PHP_EOL;
298	}
299
300	/**
301	 * Gets a single bridge card
302	 *
303	 * @param string $bridgeName The bridge name
304	 * @param array $formats A list of formats
305	 * @param bool $isActive Indicates if the bridge is active or not
306	 * @return string The bridge card
307	 */
308	static function displayBridgeCard($bridgeName, $formats, $isActive = true){
309
310		$bridgeFac = new \BridgeFactory();
311		$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
312
313		$bridge = $bridgeFac->create($bridgeName);
314
315		if($bridge == false)
316			return '';
317
318		$isHttps = strpos($bridge->getURI(), 'https') === 0;
319
320		$uri = $bridge->getURI();
321		$name = $bridge->getName();
322		$icon = $bridge->getIcon();
323		$description = $bridge->getDescription();
324		$parameters = $bridge->getParameters();
325
326		if(defined('PROXY_URL') && PROXY_BYBRIDGE) {
327			$parameters['global']['_noproxy'] = array(
328				'name' => 'Disable proxy (' . ((defined('PROXY_NAME') && PROXY_NAME) ? PROXY_NAME : PROXY_URL) . ')',
329				'type' => 'checkbox'
330			);
331		}
332
333		if(CUSTOM_CACHE_TIMEOUT) {
334			$parameters['global']['_cache_timeout'] = array(
335				'name' => 'Cache timeout in seconds',
336				'type' => 'number',
337				'defaultValue' => $bridge->getCacheTimeout()
338			);
339		}
340
341		$card = <<<CARD
342			<section id="bridge-{$bridgeName}" data-ref="{$bridgeName}">
343				<h2><a href="{$uri}">{$name}</a></h2>
344				<p class="description">{$description}</p>
345				<input type="checkbox" class="showmore-box" id="showmore-{$bridgeName}" />
346				<label class="showmore" for="showmore-{$bridgeName}">Show more</label>
347CARD;
348
349		// If we don't have any parameter for the bridge, we print a generic form to load it.
350		if (count($parameters) === 0) {
351			$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
352
353		// Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters
354		} else if (count($parameters) === 1 && array_key_exists('global', $parameters)) {
355			$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']);
356
357		} else {
358
359			foreach($parameters as $parameterName => $parameter) {
360				if(!is_numeric($parameterName) && $parameterName === 'global')
361					continue;
362
363				if(array_key_exists('global', $parameters))
364					$parameter = array_merge($parameter, $parameters['global']);
365
366				if(!is_numeric($parameterName))
367					$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
368
369				$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
370			}
371
372		}
373
374		$card .= '<label class="showless" for="showmore-' . $bridgeName . '">Show less</label>';
375		$card .= '<p class="maintainer">' . $bridge->getMaintainer() . '</p>';
376		$card .= '</section>';
377
378		return $card;
379	}
380}
381