1<?php
2/**
3 * Elgg page handler functions
4 */
5
6/**
7 * Register a new route
8 *
9 * Route paths can contain wildcard segments, i.e. /blog/owner/{username}
10 * To make a certain wildcard segment optional, add ? to its name,
11 * i.e. /blog/owner/{username?}
12 *
13 * Wildcard requirements for common named variables such as 'guid' and 'username'
14 * will be set automatically.
15 *
16 * @warning If you are registering a route in the path of a route registered by
17 *          deprecated {@link elgg_register_page_handler}, your registration must
18 *          preceed the call to elgg_register_page_handler() in the boot sequence.
19 *
20 * @param string $name   Unique route name
21 *                       This name can later be used to generate route URLs
22 * @param array  $params Route parameters
23 *                       - path : path of the route
24 *                       - resource : name of the resource view
25 *                       - defaults : default values of wildcard segments
26 *                       - requirements : regex patterns for wildcard segment requirements
27 *                       - methods : HTTP methods
28 *
29 * @return \Elgg\Router\Route
30 * @throws InvalidParameterException
31 */
32function elgg_register_route($name, array $params = []) {
33	return _elgg_services()->routes->register($name, $params);
34}
35
36/**
37 * Unregister a route by its name
38 *
39 * @param string $name Name of the route
40 *
41 * @return void
42 */
43function elgg_unregister_route($name) {
44	_elgg_services()->routes->unregister($name);
45}
46
47/**
48 * Generate a URL for named route
49 *
50 * @param string $name       Route name
51 * @param array  $parameters Parameters
52 *
53 * @return false|string
54 */
55function elgg_generate_url($name, array $parameters = []) {
56	return _elgg_services()->routes->generateUrl($name, $parameters);
57}
58
59/**
60 * Generate entity URL from a named route
61 *
62 * This function is intended to generate URLs from registered named routes that depend on entity type and subtype.
63 * It will first try to detect routes that contain both type and subtype in the name, and will then fallback to
64 * route names without the subtype, e.g. 'view:object:blog:attachments' and 'view:object:attachments'
65 *
66 * @tip Route segments will be automatically resolved from entity attributes and metadata,
67 *      so given the path `/blog/view/{guid}/{title}/{status}` the path will be
68 *      be resolved from entity guid, URL-friendly title and status metadata.
69 *
70 * @tip Parameters that do not have matching segment names in the route path, will be added to the URL as query
71 *      elements.
72 *
73 *
74 * @param ElggEntity $entity      Entity
75 * @param string     $resource    Resource name
76 * @param string     $subresource Subresource name
77 * @param array      $parameters  URL query elements
78 *
79 * @return false|string
80 */
81function elgg_generate_entity_url(ElggEntity $entity, $resource = 'view', $subresource = null, array $parameters = []) {
82
83	$make_route_name = function ($type, $subtype) use ($resource, $subresource) {
84		$route_parts = [
85			$resource,
86			$type,
87			$subtype,
88			$subresource,
89		];
90
91		return implode(':', array_filter($route_parts));
92	};
93
94	$pairs = [
95		[$entity->type, $entity->subtype],
96		[$entity->type, null],
97	];
98
99	foreach ($pairs as $pair) {
100		$route_name = $make_route_name($pair[0], $pair[1]);
101		$params = _elgg_services()->routes->resolveRouteParameters($route_name, $entity, $parameters);
102		if ($params !== false) {
103			return elgg_generate_url($route_name, $params);
104		}
105	}
106
107	return false;
108}
109
110/**
111 * Generate an action URL
112 *
113 * @param string $action          Action name
114 * @param array  $query           Query elements
115 * @param bool   $add_csrf_tokens Add tokens
116 *
117 * @return string
118 */
119function elgg_generate_action_url($action, array $query = [], $add_csrf_tokens = true) {
120
121	$url = "action/$action";
122	$url = elgg_http_add_url_query_elements($url, $query);
123	$url = elgg_normalize_url($url);
124
125	if ($add_csrf_tokens) {
126		$url = elgg_add_action_tokens_to_url($url);
127	}
128
129	return $url;
130}
131
132/**
133 * Used at the top of a page to mark it as logged in users only.
134 *
135 * @return void
136 * @throws \Elgg\Http\Exception\LoggedInGatekeeperException
137 * @since 1.9.0
138 */
139function elgg_gatekeeper() {
140	_elgg_services()->gatekeeper->assertAuthenticatedUser();
141}
142
143/**
144 * Used at the top of a page to mark it as admin only.
145 *
146 * @return void
147 * @throws \Elgg\Http\Exception\AdminGatekeeperException
148 * @since 1.9.0
149 */
150function elgg_admin_gatekeeper() {
151	_elgg_services()->gatekeeper->assertAuthenticatedAdmin();
152}
153
154/**
155 * Can the viewer see this entity?
156 *
157 * Tests if the entity exists and whether the viewer has access to the entity
158 * if it does. If the viewer cannot view this entity, it forwards to an
159 * appropriate page.
160 *
161 * @param int    $guid              Entity GUID
162 * @param string $type              Optional required entity type
163 * @param string $subtype           Optional required entity subtype
164 * @param bool   $validate_can_edit flag to check canEdit access
165 *
166 * @return void
167 *
168 * @throws Exception
169 * @throws \Elgg\EntityNotFoundException
170 * @throws \Elgg\EntityPermissionsException
171 * @throws \Elgg\HttpException
172 * @since 1.9.0
173 */
174function elgg_entity_gatekeeper($guid, $type = null, $subtype = null, $validate_can_edit = false) {
175	$entity = _elgg_services()->gatekeeper->assertExists($guid, $type, $subtype);
176	_elgg_services()->gatekeeper->assertAccessibleEntity($entity, null, $validate_can_edit);
177}
178
179/**
180 * Require that the current request be an XHR. If not, execution of the current function
181 * will end and a 400 response page will be sent.
182 *
183 * @return void
184 * @throws \Elgg\Http\Exception\AjaxGatekeeperException
185 * @since 1.12.0
186 */
187function elgg_ajax_gatekeeper() {
188	_elgg_services()->gatekeeper->assertXmlHttpRequest();
189}
190
191/**
192 * Prepares a successful response to be returned by a page or an action handler
193 *
194 * @param mixed  $content     Response content
195 *                            In page handlers, response content should contain an HTML string
196 *                            In action handlers, response content can contain either a JSON string or an array of data
197 * @param string $message     System message visible to the client
198 *                            Can be used by handlers to display a system message
199 * @param string $forward_url Forward URL
200 *                            Can be used by handlers to redirect the client on non-ajax requests
201 * @param int    $status_code HTTP status code
202 *                            Status code of the HTTP response (defaults to 200)
203 *
204 * @return \Elgg\Http\OkResponse
205 */
206function elgg_ok_response($content = '', $message = '', $forward_url = null, $status_code = ELGG_HTTP_OK) {
207	if ($message) {
208		system_message($message);
209	}
210
211	return new \Elgg\Http\OkResponse($content, $status_code, $forward_url);
212
213}
214
215/**
216 * Prepare an error response to be returned by a page or an action handler
217 *
218 * @param string $error       Error message
219 *                            Can be used by handlers to display an error message
220 *                            For certain requests this error message will also be used as the response body
221 * @param string $forward_url URL to redirect the client to
222 *                            Can be used by handlers to redirect the client on non-ajax requests
223 * @param int    $status_code HTTP status code
224 *                            Status code of the HTTP response
225 *                            For BC reasons and due to the logic in the client-side AJAX API,
226 *                            this defaults to 200. Note that the Router and AJAX API will
227 *                            treat these responses as error in spite of the HTTP code assigned
228 *
229 * @return \Elgg\Http\ErrorResponse
230 */
231function elgg_error_response($error = '', $forward_url = REFERRER, $status_code = ELGG_HTTP_OK) {
232	if ($error) {
233		register_error($error);
234	}
235
236	return new \Elgg\Http\ErrorResponse($error, $status_code, $forward_url);
237}
238
239/**
240 * Prepare a silent redirect response to be returned by a page or an action handler
241 *
242 * @param string $forward_url Redirection URL
243 *                            Relative or absolute URL to redirect the client to
244 * @param int    $status_code HTTP status code
245 *                            Status code of the HTTP response
246 *                            Note that the Router and AJAX API will treat these responses
247 *                            as redirection in spite of the HTTP code assigned
248 *                            Note that non-redirection HTTP codes will throw an exception
249 *
250 * @return \Elgg\Http\RedirectResponse
251 * @throws \InvalidArgumentException
252 */
253function elgg_redirect_response($forward_url = REFERRER, $status_code = ELGG_HTTP_FOUND) {
254	return new Elgg\Http\RedirectResponse($forward_url, $status_code);
255}
256
257/**
258 * /cron handler
259 *
260 * @param array $segments URL segments
261 *
262 * @return bool
263 * @internal
264 */
265function _elgg_cron_page_handler($segments) {
266
267	if (_elgg_config()->security_protect_cron) {
268		elgg_signed_request_gatekeeper();
269	}
270
271	$interval = elgg_strtolower(array_shift($segments));
272
273	$intervals = null;
274	if ($interval !== 'run') {
275		$intervals = [$interval];
276	}
277
278	$output = '';
279	try {
280		$force = (bool) get_input('force');
281		$jobs = _elgg_services()->cron->run($intervals, $force);
282		foreach ($jobs as $job) {
283			$output .= $job->getOutput() . PHP_EOL;
284		}
285	} catch (CronException $ex) {
286		$output .= "Exception: {$ex->getMessage()}";
287	}
288
289	echo nl2br($output);
290	return true;
291}
292