1<?php 2/** 3 * Elgg's view system. 4 * 5 * The view system is the primary templating engine in Elgg and renders 6 * all output. Views are short, parameterised PHP scripts for displaying 7 * output that can be regsitered, overridden, or extended. The view type 8 * determines the output format and location of the files that renders the view. 9 * 10 * Elgg uses a two step process to render full output: first 11 * content-specific elements are rendered, then the resulting 12 * content is inserted into a layout and displayed. This makes it 13 * easy to maintain a consistent look on all pages. 14 * 15 * A view corresponds to a single file on the filesystem and the views 16 * name is its directory structure. A file in 17 * <code>mod/plugins/views/default/myplugin/example.php</code> 18 * is called by saying (with the default viewtype): 19 * <code>echo elgg_view('myplugin/example');</code> 20 * 21 * View names that are registered later override those that are 22 * registered earlier. For plugins this corresponds directly 23 * to their load order: views in plugins lower in the list override 24 * those higher in the list. 25 * 26 * Plugin views belong in the views/ directory under an appropriate 27 * viewtype. Views are automatically registered. 28 * 29 * Views can be embedded-you can call a view from within a view. 30 * Views can also be prepended or extended by any other view. 31 * 32 * Any view can extend any other view if registered with 33 * {@link elgg_extend_view()}. 34 * 35 * Viewtypes are set by passing $_REQUEST['view']. The viewtype 36 * 'default' is a standard HTML view. Types can be defined on the fly 37 * and you can get the current viewtype with {@link elgg_get_viewtype()}. 38 * 39 * @note Internal: Plugin views are autoregistered before their init functions 40 * are called, so the init order doesn't affect views. 41 * 42 * @note Internal: The file that determines the output of the view is the last 43 * registered by {@link elgg_set_view_location()}. 44 */ 45 46use Elgg\Menu\Menu; 47use Elgg\Menu\UnpreparedMenu; 48use Elgg\Project\Paths; 49 50/** 51 * Manually set the viewtype. 52 * 53 * View types are detected automatically. This function allows 54 * you to force subsequent views to use a different viewtype. 55 * 56 * @tip Call elgg_set_viewtype() with no parameter to reset. 57 * 58 * @param string $viewtype The view type, e.g. 'rss', or 'default'. 59 * 60 * @return bool 61 */ 62function elgg_set_viewtype($viewtype = '') { 63 return _elgg_services()->views->setViewtype($viewtype); 64} 65 66/** 67 * Return the current view type. 68 * 69 * Viewtypes are automatically detected and can be set with $_REQUEST['view'] 70 * or {@link elgg_set_viewtype()}. 71 * 72 * @return string The viewtype 73 * @see elgg_set_viewtype() 74 */ 75function elgg_get_viewtype() { 76 return _elgg_services()->views->getViewtype(); 77} 78 79/** 80 * Register a viewtype to fall back to a default view if a view isn't 81 * found for that viewtype. 82 * 83 * @tip This is useful for alternate html viewtypes (such as for mobile devices). 84 * 85 * @param string $viewtype The viewtype to register 86 * 87 * @return void 88 * @since 1.7.2 89 */ 90function elgg_register_viewtype_fallback($viewtype) { 91 _elgg_services()->views->registerViewtypeFallback($viewtype); 92} 93 94/** 95 * Checks if a viewtype falls back to default. 96 * 97 * @param string $viewtype Viewtype 98 * 99 * @return boolean 100 * @since 1.7.2 101 */ 102function elgg_does_viewtype_fallback($viewtype) { 103 return _elgg_services()->views->doesViewtypeFallback($viewtype); 104} 105 106/** 107 * Register a view to be available for ajax calls 108 * 109 * @warning Only views that begin with 'js/' and 'css/' have their content 110 * type set to 'text/javascript' and 'text/css'. Other views are served as 111 * 'text/html'. 112 * 113 * @param string $view The view name 114 * @return void 115 * @since 1.8.3 116 */ 117function elgg_register_ajax_view($view) { 118 elgg_register_external_view($view, false); 119} 120 121/** 122 * Unregister a view for ajax calls 123 * 124 * @param string $view The view name 125 * @return void 126 * @since 1.8.3 127 */ 128function elgg_unregister_ajax_view($view) { 129 elgg_unregister_external_view($view); 130} 131 132/** 133 * Registers a view as being available externally (i.e. via URL). 134 * 135 * @param string $view The name of the view. 136 * @param boolean $cacheable Whether this view can be cached. 137 * @return void 138 * @since 1.9.0 139 */ 140function elgg_register_external_view($view, $cacheable = false) { 141 142 _elgg_services()->ajax->registerView($view); 143 144 if ($cacheable) { 145 _elgg_services()->views->registerCacheableView($view); 146 } 147} 148 149/** 150 * Unregister a view for ajax calls 151 * 152 * @param string $view The view name 153 * @return void 154 * @since 1.9.0 155 */ 156function elgg_unregister_external_view($view) { 157 _elgg_services()->ajax->unregisterView($view); 158} 159 160/** 161 * Set an alternative base location for a view. 162 * 163 * Views are expected to be in plugin_name/views/. This function can 164 * be used to change that location. 165 * 166 * @tip This is useful to optionally register views in a plugin. 167 * 168 * @param string $view The name of the view 169 * @param string $location The full path to the view 170 * @param string $viewtype The view type 171 * 172 * @return void 173 */ 174function elgg_set_view_location($view, $location, $viewtype = '') { 175 _elgg_services()->views->setViewDir($view, $location, $viewtype); 176} 177 178/** 179 * Returns whether the specified view exists 180 * 181 * @note If $recurse is true, also checks if a view exists only as an extension. 182 * 183 * @param string $view The view name 184 * @param string $viewtype If set, forces the viewtype 185 * @param bool $recurse If false, do not check extensions 186 * 187 * @return bool 188 */ 189function elgg_view_exists($view, $viewtype = '', $recurse = true) { 190 return _elgg_services()->views->viewExists($view, $viewtype, $recurse); 191} 192 193/** 194 * Return a parsed view. 195 * 196 * Views are rendered by a template handler and returned as strings. 197 * 198 * Views are called with a special $vars variable set, 199 * which includes any variables passed as the second parameter. 200 * 201 * The input of views can be intercepted by registering for the 202 * view_vars, $view_name plugin hook. 203 * 204 * If the input contains the key "__view_output", the view will output this value as a string. 205 * No extensions are used, and the "view" hook is not triggered). 206 * 207 * The output of views can be intercepted by registering for the 208 * view, $view_name plugin hook. 209 * 210 * @param string $view The name and location of the view to use 211 * @param array $vars Variables to pass to the view. 212 * @param string $viewtype If set, forces the viewtype for the elgg_view call to be 213 * this value (default: standard detection) 214 * 215 * @return string The parsed view 216 */ 217function elgg_view($view, $vars = [], $viewtype = '') { 218 if (func_num_args() == 5) { 219 elgg_log(__FUNCTION__ . ' now has only 3 arguments. Update your usage.', 'ERROR'); 220 $viewtype = func_get_arg(4); 221 } 222 return _elgg_services()->views->renderView($view, $vars, $viewtype); 223} 224 225/** 226 * Display a view with a deprecation notice. No missing view NOTICE is logged 227 * 228 * @param string $view The name and location of the view to use 229 * @param array $vars Variables to pass to the view 230 * @param string $suggestion Suggestion with the deprecation message 231 * @param string $version Human-readable *release* version: 1.7, 1.8, ... 232 * 233 * @return string The parsed view 234 * 235 * @see elgg_view() 236 */ 237function elgg_view_deprecated($view, array $vars, $suggestion, $version) { 238 return _elgg_services()->views->renderDeprecatedView($view, $vars, $suggestion, $version); 239} 240 241/** 242 * Extends a view with another view. 243 * 244 * The output of any view can be prepended or appended to any other view. 245 * 246 * The default action is to append a view. If the priority is less than 500, 247 * the output of the extended view will be appended to the original view. 248 * 249 * Views can be extended multiple times, and extensions are not checked for 250 * uniqueness. Use {@link elgg_unextend_view()} to help manage duplicates. 251 * 252 * Priority can be specified and affects the order in which extensions 253 * are appended or prepended. 254 * 255 * @see elgg_prepend_css_urls() If the extension is CSS, you may need to use this to fix relative URLs. 256 * 257 * @param string $view The view to extend. 258 * @param string $view_extension This view is added to $view 259 * @param int $priority The priority, from 0 to 1000, to add at (lowest numbers displayed first) 260 * 261 * @return void 262 * @since 1.7.0 263 */ 264function elgg_extend_view($view, $view_extension, $priority = 501) { 265 _elgg_services()->views->extendView($view, $view_extension, $priority); 266} 267 268/** 269 * Unextends a view. 270 * 271 * @param string $view The view that was extended. 272 * @param string $view_extension This view that was added to $view 273 * 274 * @return bool 275 * @since 1.7.2 276 */ 277function elgg_unextend_view($view, $view_extension) { 278 return _elgg_services()->views->unextendView($view, $view_extension); 279} 280 281/** 282 * Get the views (and priorities) that extend a view. 283 * 284 * @note extensions may change anytime, especially during the [init, system] event 285 * 286 * @param string $view View name 287 * 288 * @return string[] Keys returned are view priorities. 289 * @since 2.3 290 */ 291function elgg_get_view_extensions($view) { 292 $list = _elgg_services()->views->getViewList($view); 293 unset($list[500]); 294 return $list; 295} 296 297/** 298 * In CSS content, prepend a path to relative URLs. 299 * 300 * This is useful to process a CSS view being used as an extension. 301 * 302 * @param string $css CSS 303 * @param string $path Path to prepend. E.g. "foo/bar/" or "../" 304 * 305 * @return string 306 * @since 2.2 307 */ 308function elgg_prepend_css_urls($css, $path) { 309 return Minify_CSS_UriRewriter::prepend($css, $path); 310} 311 312/** 313 * Assembles and outputs a full page. 314 * 315 * A "page" in Elgg is determined by the current view type and 316 * can be HTML for a browser, RSS for a feed reader, or 317 * Javascript, PHP and a number of other formats. 318 * 319 * For HTML pages, use the 'head', 'page' plugin hook for setting meta elements 320 * and links. 321 * 322 * @param string $title Title 323 * @param string|array $body Body as a string or as an array (which will be passed to elgg_view_layout('default', $body) 324 * @param string $page_shell Optional page shell to use. See page/shells view directory 325 * @param array $vars Optional vars array to pass to the page 326 * shell. Automatically adds title, body, head, and sysmessages 327 * 328 * @return string The contents of the page 329 * @since 1.8 330 */ 331function elgg_view_page($title, $body, $page_shell = 'default', $vars = []) { 332 333 if (is_array($body)) { 334 $body['title'] = elgg_extract('title', $body, $title); 335 $body = elgg_view_layout('default', $body); 336 } 337 338 $timer = _elgg_services()->timer; 339 if (!$timer->hasEnded(['build page'])) { 340 $timer->end(['build page']); 341 } 342 $timer->begin([__FUNCTION__]); 343 344 $params = []; 345 $params['identifier'] = _elgg_services()->request->getFirstUrlSegment(); 346 $params['segments'] = _elgg_services()->request->getUrlSegments(); 347 array_shift($params['segments']); 348 $page_shell = elgg_trigger_plugin_hook('shell', 'page', $params, $page_shell); 349 350 351 $system_messages = _elgg_services()->systemMessages; 352 353 $messages = null; 354 if ($system_messages->count()) { 355 $messages = $system_messages->dumpRegister(); 356 357 if (isset($messages['error'])) { 358 // always make sure error is the first type 359 $errors = [ 360 'error' => $messages['error'] 361 ]; 362 363 unset($messages['error']); 364 $messages = array_merge($errors, $messages); 365 } 366 } 367 368 $vars['title'] = $title; 369 $vars['body'] = $body; 370 $vars['sysmessages'] = $messages; 371 $vars['page_shell'] = $page_shell; 372 373 // head has keys 'title', 'metas', 'links' 374 $head_params = _elgg_views_prepare_head($title); 375 376 $vars['head'] = elgg_trigger_plugin_hook('head', 'page', $vars, $head_params); 377 378 $vars = elgg_trigger_plugin_hook('output:before', 'page', null, $vars); 379 380 $output = elgg_view("page/$page_shell", $vars); 381 382 383 // Allow plugins to modify the output 384 $output = elgg_trigger_plugin_hook('output', 'page', $vars, $output); 385 386 $timer->end([__FUNCTION__]); 387 return $output; 388} 389 390/** 391 * Render a resource view. Use this in your page handler to hand off page rendering to 392 * a view in "resources/". If not found in the current viewtype, we try the "default" viewtype. 393 * 394 * @param string $name The view name without the leading "resources/" 395 * @param array $vars Arguments passed to the view 396 * 397 * @return string 398 * @throws \Elgg\PageNotFoundException 399 */ 400function elgg_view_resource($name, array $vars = []) { 401 $view = "resources/$name"; 402 403 if (elgg_view_exists($view)) { 404 return _elgg_services()->views->renderView($view, $vars); 405 } 406 407 if (elgg_get_viewtype() !== 'default' && elgg_view_exists($view, 'default')) { 408 return _elgg_services()->views->renderView($view, $vars, 'default'); 409 } 410 411 _elgg_services()->logger->error("The view $view is missing."); 412 413 // only works for default viewtype 414 throw new \Elgg\PageNotFoundException(); 415} 416 417/** 418 * Prepare the variables for the html head 419 * 420 * @param string $title Page title for <head> 421 * @return array 422 * @internal 423 */ 424function _elgg_views_prepare_head($title) { 425 $params = [ 426 'links' => [], 427 'metas' => [], 428 ]; 429 430 if (empty($title)) { 431 $params['title'] = _elgg_config()->sitename; 432 } else { 433 $params['title'] = $title . ' : ' . _elgg_config()->sitename; 434 } 435 436 $params['metas']['content-type'] = [ 437 'http-equiv' => 'Content-Type', 438 'content' => 'text/html; charset=utf-8', 439 ]; 440 441 $params['metas']['description'] = [ 442 'name' => 'description', 443 'content' => _elgg_config()->sitedescription 444 ]; 445 446 // https://developer.chrome.com/multidevice/android/installtohomescreen 447 $params['metas']['viewport'] = [ 448 'name' => 'viewport', 449 'content' => 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0', 450 ]; 451 $params['metas']['mobile-web-app-capable'] = [ 452 'name' => 'mobile-web-app-capable', 453 'content' => 'yes', 454 ]; 455 $params['metas']['apple-mobile-web-app-capable'] = [ 456 'name' => 'apple-mobile-web-app-capable', 457 'content' => 'yes', 458 ]; 459 460 // RSS feed link 461 if (_elgg_has_rss_link()) { 462 $url = current_page_url(); 463 if (elgg_substr_count($url, '?')) { 464 $url .= "&view=rss"; 465 } else { 466 $url .= "?view=rss"; 467 } 468 $params['links']['rss'] = [ 469 'rel' => 'alternative', 470 'type' => 'application/rss+xml', 471 'title' => 'RSS', 472 'href' => $url, 473 ]; 474 } 475 476 return $params; 477} 478 479 480/** 481 * Add favicon link tags to HTML head 482 * 483 * @param \Elgg\Hook $hook "head", "page" 484 * returnvalue contains head params 485 * <code> 486 * [ 487 * 'title' => '', 488 * 'metas' => [], 489 * 'links' => [], 490 * ] 491 * </code> 492 * 493 * @return array 494 */ 495function _elgg_views_prepare_favicon_links(\Elgg\Hook $hook) { 496 $head_params = $hook->getValue(); 497 498 $head_params['links']['apple-touch-icon'] = [ 499 'rel' => 'apple-touch-icon', 500 'href' => elgg_get_simplecache_url('graphics/favicon-128.png'), 501 ]; 502 503 // favicons 504 $head_params['links']['icon-ico'] = [ 505 'rel' => 'icon', 506 'href' => elgg_get_simplecache_url('graphics/favicon.ico'), 507 ]; 508 $head_params['links']['icon-vector'] = [ 509 'rel' => 'icon', 510 'sizes' => '16x16 32x32 48x48 64x64 128x128', 511 'type' => 'image/svg+xml', 512 'href' => elgg_get_simplecache_url('graphics/favicon.svg'), 513 ]; 514 $head_params['links']['icon-16'] = [ 515 'rel' => 'icon', 516 'sizes' => '16x16', 517 'type' => 'image/png', 518 'href' => elgg_get_simplecache_url('graphics/favicon-16.png'), 519 ]; 520 $head_params['links']['icon-32'] = [ 521 'rel' => 'icon', 522 'sizes' => '32x32', 523 'type' => 'image/png', 524 'href' => elgg_get_simplecache_url('graphics/favicon-32.png'), 525 ]; 526 $head_params['links']['icon-64'] = [ 527 'rel' => 'icon', 528 'sizes' => '64x64', 529 'type' => 'image/png', 530 'href' => elgg_get_simplecache_url('graphics/favicon-64.png'), 531 ]; 532 $head_params['links']['icon-128'] = [ 533 'rel' => 'icon', 534 'sizes' => '128x128', 535 'type' => 'image/png', 536 'href' => elgg_get_simplecache_url('graphics/favicon-128.png'), 537 ]; 538 539 return $head_params; 540} 541 542/** 543 * Displays a layout with optional parameters. 544 * 545 * Layouts are templates provide consistency by organizing blocks of content on the page. 546 * 547 * Plugins should use one of the core layouts: 548 * - default Primary template with one, two or no sidebars 549 * - admin Admin page template 550 * - error Error page template 551 * - widgets Widgets canvas 552 * 553 * Plugins can create and use custom layouts by placing a layout view 554 * in "page/layouts/<layout_name>" and calling elgg_view_layout(<layout_name>). 555 * 556 * For a full list of parameters supported by each of these layouts see 557 * corresponding layout views. 558 * 559 * @param string $layout_name Layout name 560 * Corresponds to a view in "page/layouts/<layout_name>". 561 * @param array $vars Layout parameters 562 * An associative array of parameters to pass to 563 * the layout hooks and views. 564 * Route 'identifier' and 'segments' of the page being 565 * rendered will be added to this array automatially, 566 * allowing plugins to alter layout views and subviews 567 * based on the current route. 568 * @return string 569 */ 570function elgg_view_layout($layout_name, $vars = []) { 571 $timer = _elgg_services()->timer; 572 if (!$timer->hasEnded(['build page'])) { 573 $timer->end(['build page']); 574 } 575 $timer->begin([__FUNCTION__]); 576 577 if (in_array($layout_name, ['content', 'one_sidebar', 'one_column', 'two_sidebar'])) { 578 elgg_deprecated_notice("Using the '{$layout_name}' layout is deprecated. Please update your code to use the 'default' layout.", '3.3'); 579 } 580 581 if ($layout_name !== 'content' && isset($vars['filter_context'])) { 582 elgg_deprecated_notice("Using 'filter_context' to set the active menu item is not supported. Please update your code to use the 'filter_value' var.", '3.3'); 583 } 584 585 // Help plugins transition without breaking them 586 switch ($layout_name) { 587 case 'content' : 588 $layout_name = 'default'; 589 $vars = _elgg_normalize_content_layout_vars($vars); 590 break; 591 592 case 'one_sidebar' : 593 $layout_name = 'default'; 594 $vars['sidebar'] = elgg_extract('sidebar', $vars, '', false); 595 $vars['sidebar_alt'] = false; 596 break; 597 598 case 'one_column' : 599 $layout_name = 'default'; 600 $vars['sidebar'] = false; 601 $vars['sidebar_alt'] = false; 602 break; 603 604 case 'two_sidebar' : 605 $layout_name = 'default'; 606 $vars['sidebar'] = elgg_extract('sidebar', $vars, '', false); 607 $vars['sidebar_alt'] = elgg_extract('sidebar_alt', $vars, '', false); 608 break; 609 610 case 'default' : 611 $filter_id = elgg_extract('filter_id', $vars, 'filter'); 612 $filter_context = elgg_extract('filter_value', $vars); 613 if (isset($filter_context) && $filter_id === 'filter') { 614 $context = elgg_extract('context', $vars, elgg_get_context()); 615 $vars['filter'] = elgg_get_filter_tabs($context, $filter_context, null, $vars); 616 $vars['filter_id'] = $filter_id; 617 $vars['filter_value'] = $filter_context; 618 } 619 break; 620 } 621 622 if (isset($vars['nav'])) { 623 // Temporary helper until all core views are updated 624 $vars['breadcrumbs'] = $vars['nav']; 625 unset($vars['nav']); 626 } 627 628 $vars['identifier'] = _elgg_services()->request->getFirstUrlSegment(); 629 $vars['segments'] = _elgg_services()->request->getUrlSegments(); 630 array_shift($vars['segments']); 631 632 $layout_name = elgg_trigger_plugin_hook('layout', 'page', $vars, $layout_name); 633 634 $vars['layout'] = $layout_name; 635 636 $layout_views = [ 637 "page/layouts/$layout_name", 638 "page/layouts/default", 639 ]; 640 641 $output = ''; 642 foreach ($layout_views as $layout_view) { 643 if (elgg_view_exists($layout_view)) { 644 $output = elgg_view($layout_view, $vars); 645 break; 646 } 647 } 648 649 $timer->end([__FUNCTION__]); 650 return $output; 651} 652 653/** 654 * Normalizes deprecated content layout $vars for use in default layout 655 * Helper function to assist plugins transitioning to 3.0 656 * 657 * @param array $vars Vars 658 * @return array 659 * @internal 660 */ 661function _elgg_normalize_content_layout_vars(array $vars = []) { 662 663 $context = elgg_extract('context', $vars, elgg_get_context()); 664 665 $vars['title'] = elgg_extract('title', $vars, ''); 666 if (!$vars['title'] && $vars['title'] !== false) { 667 $vars['title'] = elgg_echo($context); 668 } 669 670 // 1.8 supported 'filter_override' 671 if (isset($vars['filter_override'])) { 672 $vars['filter'] = $vars['filter_override']; 673 } 674 675 // register the default content filters 676 if (!isset($vars['filter']) && $context) { 677 $selected = elgg_extract('filter_context', $vars); 678 $vars['filter'] = elgg_get_filter_tabs($context, $selected, null, $vars); 679 $vars['filter_id'] = $context; 680 $vars['filter_value'] = $selected; 681 } 682 683 return $vars; 684} 685 686/** 687 * Render a menu 688 * 689 * @see elgg_register_menu_item() for documentation on adding menu items and 690 * navigation.php for information on the different menus available. 691 * 692 * This function triggers a 'register', 'menu:<menu name>' plugin hook that enables 693 * plugins to add menu items just before a menu is rendered. This is used by 694 * dynamic menus (menus that change based on some input such as the user hover 695 * menu). Using elgg_register_menu_item() in response to the hook can cause 696 * incorrect links to show up. See the blog plugin's blog_owner_block_menu() 697 * for an example of using this plugin hook. 698 * 699 * An additional hook is the 'prepare', 'menu:<menu name>' which enables plugins 700 * to modify the structure of the menu (sort it, remove items, set variables on 701 * the menu items). 702 * 703 * Preset (unprepared) menu items passed to the this function with the $vars 704 * argument, will be merged with the registered items (registered with 705 * elgg_register_menu_item()). The combined set of menu items will be passed 706 * to 'register', 'menu:<menu_name>' hook. 707 * 708 * Plugins that pass preset menu items to this function and do not wish to be 709 * affected by plugin hooks (e.g. if you are displaying multiple menus with 710 * the same name on the page) should instead choose a unqie menu name 711 * and define a menu_view argument to render menus consistently. 712 * For example, if you have multiple 'filter' menus on the page: 713 * <code> 714 * elgg_view_menu("filter:$uid", [ 715 * 'items' => $items, 716 * 'menu_view' => 'navigation/menu/filter', 717 * ]); 718 * </code> 719 * 720 * elgg_view_menu() uses views in navigation/menu 721 * 722 * @param string|Menu|UnpreparedMenu $menu Menu name (or object) 723 * @param array $vars An associative array of display options for the menu. 724 * 725 * Options include: 726 * items => (array) an array of unprepared menu items as ElggMenuItem or menu item factory options 727 * sort_by => (string) or php callback string options: 'name', 'priority' (default), 'text' 728 * or a php callback (a compare function for usort) 729 * handler => (string) the page handler to build action URLs 730 * entity => (\ElggEntity) entity to use to build action URLs 731 * class => (string) the class for the entire menu 732 * menu_view => (string) name of the view to be used to render the menu 733 * show_section_headers => (bool) show headers before menu sections 734 * selected_item_name => (string) the menu item name to be selected 735 * 736 * @return string 737 * @since 1.8.0 738 */ 739function elgg_view_menu($menu, array $vars = []) { 740 741 $menu_view = elgg_extract('menu_view', $vars); 742 unset($vars['menu_view']); 743 744 if (is_string($menu)) { 745 $menu = _elgg_services()->menus->getMenu($menu, $vars); 746 } elseif ($menu instanceof UnpreparedMenu) { 747 $menu = _elgg_services()->menus->prepareMenu($menu); 748 } 749 750 if (!$menu instanceof Menu) { 751 throw new \InvalidArgumentException('$menu must be a menu name, a Menu, or UnpreparedMenu'); 752 } 753 754 $name = $menu->getName(); 755 $params = $menu->getParams(); 756 757 $views = [ 758 $menu_view, 759 "navigation/menu/$name", 760 'navigation/menu/default', 761 ]; 762 763 foreach ($views as $view) { 764 if (elgg_view_exists($view)) { 765 return elgg_view($view, $params); 766 } 767 } 768} 769 770/** 771 * Render a menu item (usually as a link) 772 * 773 * @param \ElggMenuItem $item The menu item 774 * @param array $vars Options to pass to output/url if a link 775 * @return string 776 * @since 1.9.0 777 */ 778function elgg_view_menu_item(\ElggMenuItem $item, array $vars = []) { 779 780 $vars = array_merge($item->getValues(), $vars); 781 $vars['class'] = elgg_extract_class($vars, ['elgg-menu-content']); 782 783 if ($item->getLinkClass()) { 784 $vars['class'][] = $item->getLinkClass(); 785 } 786 787 if ($item->getHref() === false || $item->getHref() === null) { 788 $vars['class'][] = 'elgg-non-link'; 789 } 790 791 if (!isset($vars['rel']) && !isset($vars['is_trusted'])) { 792 $vars['is_trusted'] = true; 793 } 794 795 if ($item->getConfirmText()) { 796 $vars['confirm'] = $item->getConfirmText(); 797 } 798 799 return elgg_view('output/url', $vars); 800} 801 802/** 803 * Returns a string of a rendered entity. 804 * 805 * Entity views are either determined by setting the view property on the entity 806 * or by having a view named after the entity $type/$subtype. Entities that have 807 * neither a view property nor a defined $type/$subtype view will fall back to 808 * using the $type/default view. 809 * 810 * The entity view is called with the following in $vars: 811 * - \ElggEntity 'entity' The entity being viewed 812 * 813 * @param \ElggEntity $entity The entity to display 814 * @param array $vars Array of variables to pass to the entity view. 815 * 'full_view' Whether to show a full or condensed view. (Default: true) 816 * 'item_view' Alternative view used to render this entity 817 * 'register_rss_link' Register the rss link availability (default: depending on full_view) 818 * 819 * @return false|string HTML to display or false 820 * @todo The annotation hook might be better as a generic plugin hook to append content. 821 */ 822function elgg_view_entity(\ElggEntity $entity, array $vars = []) { 823 824 $defaults = [ 825 'full_view' => true, 826 ]; 827 828 $vars = array_merge($defaults, $vars); 829 830 if (elgg_extract('register_rss_link', $vars, elgg_extract('full_view', $vars))) { 831 elgg_register_rss_link(); 832 } 833 834 $vars['entity'] = $entity; 835 836 $entity_type = $entity->getType(); 837 $entity_subtype = $entity->getSubtype(); 838 839 $entity_views = [ 840 elgg_extract('item_view', $vars, ''), 841 "$entity_type/$entity_subtype", 842 "$entity_type/default", 843 ]; 844 845 $contents = ''; 846 foreach ($entity_views as $view) { 847 if (elgg_view_exists($view)) { 848 $contents = elgg_view($view, $vars); 849 break; 850 } 851 } 852 853 // Marcus Povey 20090616 : Speculative and low impact approach for fixing #964 854 if ($vars['full_view']) { 855 $annotations = elgg_view_entity_annotations($entity, $vars['full_view']); 856 857 if ($annotations) { 858 $contents .= $annotations; 859 } 860 } 861 return $contents; 862} 863 864/** 865 * View the icon of an entity 866 * 867 * Entity views are determined by having a view named after the entity $type/$subtype. 868 * Entities that do not have a defined icon/$type/$subtype view will fall back to using 869 * the icon/$type/default view. 870 * 871 * @param \ElggEntity $entity The entity to display 872 * @param string $size The size: tiny, small, medium, large 873 * @param array $vars An array of variables to pass to the view. Some possible 874 * variables are img_class and link_class. See the 875 * specific icon view for more parameters. 876 * 877 * @return string HTML to display or false 878 */ 879function elgg_view_entity_icon(\ElggEntity $entity, $size = 'medium', $vars = []) { 880 881 $vars['entity'] = $entity; 882 $vars['size'] = $size; 883 884 $entity_type = $entity->getType(); 885 886 $subtype = $entity->getSubtype(); 887 888 $contents = ''; 889 if (elgg_view_exists("icon/$entity_type/$subtype")) { 890 $contents = elgg_view("icon/$entity_type/$subtype", $vars); 891 } 892 if (empty($contents) && elgg_view_exists("icon/$entity_type/default")) { 893 $contents = elgg_view("icon/$entity_type/default", $vars); 894 } 895 if (empty($contents)) { 896 $contents = elgg_view("icon/default", $vars); 897 } 898 899 return $contents; 900} 901 902/** 903 * Returns a string of a rendered annotation. 904 * 905 * Annotation views are expected to be in annotation/$annotation_name. 906 * If a view is not found for $annotation_name, the default annotation/default 907 * will be used. 908 * 909 * The annotation view is called with the following in $vars: 910 * - \ElggEntity 'annotation' The annotation being viewed. 911 * 912 * @param \ElggAnnotation $annotation The annotation to display 913 * @param array $vars Variable array for view. 914 * 'item_view' Alternative view used to render an annotation 915 * 916 * @return string|false Rendered annotation 917 */ 918function elgg_view_annotation(\ElggAnnotation $annotation, array $vars = []) { 919 $defaults = [ 920 'full_view' => true, 921 ]; 922 923 $vars = array_merge($defaults, $vars); 924 $vars['annotation'] = $annotation; 925 926 $name = $annotation->name; 927 if (empty($name)) { 928 return false; 929 } 930 931 $annotation_views = [ 932 elgg_extract('item_view', $vars, ''), 933 "annotation/$name", 934 "annotation/default", 935 ]; 936 937 $contents = ''; 938 foreach ($annotation_views as $view) { 939 if (elgg_view_exists($view)) { 940 $contents = elgg_view($view, $vars); 941 break; 942 } 943 } 944 945 return $contents; 946} 947 948/** 949 * Returns a rendered list of entities with pagination. This function should be 950 * called by wrapper functions. 951 * 952 * @see elgg_list_entities() 953 * 954 * @param array $entities Array of entities 955 * @param array $vars Display variables 956 * 'count' The total number of entities across all pages 957 * 'offset' The current indexing offset 958 * 'limit' The number of entities to display per page (default from settings) 959 * 'full_view' Display the full view of the entities? 960 * 'list_class' CSS class applied to the list 961 * 'item_class' CSS class applied to the list items 962 * 'item_view' Alternative view to render list items content 963 * 'list_item_view' Alternative view to render list items 964 * 'pagination' Display pagination? 965 * 'base_url' Base URL of list (optional) 966 * 'url_fragment' URL fragment to add to links if not present in base_url (optional) 967 * 'position' Position of the pagination: before, after, or both 968 * 'list_type' List type: 'list' (default), 'gallery' 969 * 'list_type_toggle' Display the list type toggle? 970 * 'no_results' Message to display if no results (string|true|Closure) 971 * 972 * @return string The rendered list of entities 973 */ 974function elgg_view_entity_list($entities, array $vars = []) { 975 $offset = (int) get_input('offset', 0); 976 977 // list type can be passed as request parameter 978 $list_type = get_input('list_type', 'list'); 979 980 $defaults = [ 981 'items' => $entities, 982 'list_class' => 'elgg-list-entity', 983 'full_view' => true, 984 'pagination' => true, 985 'list_type' => $list_type, 986 'list_type_toggle' => false, 987 'offset' => $offset, 988 'limit' => null, 989 ]; 990 991 $vars = array_merge($defaults, $vars); 992 993 if (!$vars["limit"] && !$vars["offset"]) { 994 // no need for pagination if listing is unlimited 995 $vars["pagination"] = false; 996 } 997 998 $view = "page/components/{$vars['list_type']}"; 999 if (!elgg_view_exists($view)) { 1000 $view = 'page/components/list'; 1001 } 1002 1003 return elgg_view($view, $vars); 1004} 1005 1006/** 1007 * Returns a rendered list of annotations, plus pagination. This function 1008 * should be called by wrapper functions. 1009 * 1010 * @param array $annotations Array of annotations 1011 * @param array $vars Display variables 1012 * 'count' The total number of annotations across all pages 1013 * 'offset' The current indexing offset 1014 * 'limit' The number of annotations to display per page 1015 * 'full_view' Display the full view of the annotation? 1016 * 'list_class' CSS Class applied to the list 1017 * 'item_view' Alternative view to render list items 1018 * 'offset_key' The url parameter key used for offset 1019 * 'no_results' Message to display if no results (string|true|Closure) 1020 * 'list_type' List type: 'list' (default), 'gallery' 1021 * 1022 * @return string The list of annotations 1023 * @internal 1024 */ 1025function elgg_view_annotation_list($annotations, array $vars = []) { 1026 // list type can be passed as request parameter 1027 $list_type = get_input('list_type', 'list'); 1028 1029 $defaults = [ 1030 'items' => $annotations, 1031 'offset' => null, 1032 'limit' => null, 1033 'list_class' => 'elgg-list-annotation elgg-annotation-list', // @todo remove elgg-annotation-list in Elgg 1.9 1034 'full_view' => true, 1035 'list_type' => $list_type, 1036 'offset_key' => 'annoff', 1037 ]; 1038 1039 $vars = array_merge($defaults, $vars); 1040 1041 if (!$vars["limit"] && !$vars["offset"]) { 1042 // no need for pagination if listing is unlimited 1043 $vars["pagination"] = false; 1044 } 1045 1046 $view = "page/components/{$vars['list_type']}"; 1047 if (!elgg_view_exists($view)) { 1048 $view = 'page/components/list'; 1049 } 1050 1051 return elgg_view($view, $vars); 1052} 1053 1054/** 1055 * Returns a rendered list of relationships, plus pagination. This function 1056 * should be called by wrapper functions. 1057 * 1058 * @param array $relationships Array of relationships 1059 * @param array $vars Display variables 1060 * 'count' The total number of relationships across all pages 1061 * 'offset' The current indexing offset 1062 * 'limit' The number of relationships to display per page 1063 * 'full_view' Display the full view of the relationships? 1064 * 'list_class' CSS Class applied to the list 1065 * 'list_type' List type: 'list' (default), 'gallery' 1066 * 'item_view' Alternative view to render list items 1067 * 'offset_key' The url parameter key used for offset 1068 * 'no_results' Message to display if no results (string|true|Closure) 1069 * 1070 * @return string The list of relationships 1071 * @internal 1072 */ 1073function elgg_view_relationship_list($relationships, array $vars = []) { 1074 // list type can be passed as request parameter 1075 $list_type = get_input('list_type', 'list'); 1076 1077 $defaults = [ 1078 'items' => $relationships, 1079 'offset' => null, 1080 'limit' => null, 1081 'list_class' => 'elgg-list-relationship', 1082 'full_view' => false, 1083 'list_type' => $list_type, 1084 'offset_key' => 'reloff', 1085 ]; 1086 1087 $vars = array_merge($defaults, $vars); 1088 1089 if (!$vars['limit'] && !$vars['offset']) { 1090 // no need for pagination if listing is unlimited 1091 $vars['pagination'] = false; 1092 } 1093 1094 $view = "page/components/{$vars['list_type']}"; 1095 if (!elgg_view_exists($view)) { 1096 $view = 'page/components/list'; 1097 } 1098 1099 return elgg_view($view, $vars); 1100} 1101 1102/** 1103 * Returns a string of a rendered relationship. 1104 * 1105 * Relationship views are expected to be in relationship/$relationship_name. 1106 * If a view is not found for $relationship_name, the default relationship/default 1107 * will be used. 1108 * 1109 * The relationship view is called with the following in $vars: 1110 * - \ElggRelationship 'relationship' The relationship being viewed. 1111 * 1112 * @param \ElggRelationship $relationship The relationship to display 1113 * @param array $vars Variable array for view. 1114 * 'item_view' Alternative view used to render a relationship 1115 * 1116 * @return string|false Rendered relationship 1117 */ 1118function elgg_view_relationship(\ElggRelationship $relationship, array $vars = []) { 1119 $defaults = [ 1120 'full_view' => true, 1121 ]; 1122 1123 $vars = array_merge($defaults, $vars); 1124 $vars['relationship'] = $relationship; 1125 1126 $name = $relationship->relationship; 1127 if (empty($name)) { 1128 return false; 1129 } 1130 1131 $relationship_views = [ 1132 elgg_extract('item_view', $vars, ''), 1133 "relationship/$name", 1134 "relationship/default", 1135 ]; 1136 1137 $contents = ''; 1138 foreach ($relationship_views as $view) { 1139 if (elgg_view_exists($view)) { 1140 $contents = elgg_view($view, $vars); 1141 break; 1142 } 1143 } 1144 1145 return $contents; 1146} 1147 1148/** 1149 * Renders a title. 1150 * 1151 * This is a shortcut for {@elgg_view page/elements/title}. 1152 * 1153 * @param string $title The page title 1154 * @param array $vars View variables (was submenu be displayed? (deprecated)) 1155 * 1156 * @return string The HTML (etc) 1157 */ 1158function elgg_view_title($title, array $vars = []) { 1159 $vars['title'] = $title; 1160 1161 return elgg_view('page/elements/title', $vars); 1162} 1163 1164/** 1165 * Displays a UNIX timestamp in a friendly way 1166 * 1167 * @see elgg_get_friendly_time() 1168 * 1169 * @param int $time A UNIX epoch timestamp 1170 * 1171 * @return string The friendly time HTML 1172 * @since 1.7.2 1173 */ 1174function elgg_view_friendly_time($time) { 1175 $view = 'output/friendlytime'; 1176 $vars = ['time' => $time]; 1177 $viewtype = elgg_view_exists($view) ? '' : 'default'; 1178 1179 return _elgg_view_under_viewtype($view, $vars, $viewtype); 1180} 1181 1182/** 1183 * Returns rendered comments and a comment form for an entity. 1184 * 1185 * @tip Plugins can override the output by registering a handler 1186 * for the comments, $entity_type hook. The handler is responsible 1187 * for formatting the comments and the add comment form. 1188 * 1189 * @param \ElggEntity $entity The entity to view comments of 1190 * @param bool $add_comment Include a form to add comments? 1191 * @param array $vars Variables to pass to comment view 1192 * 1193 * @return string|false Rendered comments or false on failure 1194 */ 1195function elgg_view_comments($entity, $add_comment = true, array $vars = []) { 1196 1197 if (!$entity instanceof \ElggEntity) { 1198 return false; 1199 } 1200 1201 $vars['entity'] = $entity; 1202 $vars['show_add_form'] = $add_comment; 1203 $vars['class'] = elgg_extract('class', $vars, "{$entity->getSubtype()}-comments"); 1204 1205 $output = elgg_trigger_plugin_hook('comments', $entity->getType(), $vars, false); 1206 if ($output !== false) { 1207 return $output; 1208 } 1209 1210 return elgg_view('page/elements/comments', $vars); 1211} 1212 1213/** 1214 * Wrapper function for the image block display pattern. 1215 * 1216 * Fixed width media on the side (image, icon, flash, etc.). 1217 * Descriptive content filling the rest of the column. 1218 * 1219 * @note Use the $vars "image_alt" key to set an image on the right. If you do, you may pass 1220 * in an empty string for $image to have only the right image. 1221 * 1222 * This is a shortcut for {@elgg_view page/components/image_block}. 1223 * 1224 * @param string $image The icon and other information 1225 * @param string $body Description content 1226 * @param array $vars Additional parameters for the view 1227 * 1228 * @return string 1229 * @since 1.8.0 1230 */ 1231function elgg_view_image_block($image, $body, $vars = []) { 1232 $vars['image'] = $image; 1233 $vars['body'] = $body; 1234 return elgg_view('page/components/image_block', $vars); 1235} 1236 1237/** 1238 * Wrapper function for the module display pattern. 1239 * 1240 * Box with header, body, footer 1241 * 1242 * This is a shortcut for {@elgg_view page/components/module}. 1243 * 1244 * @param string $type The type of module (main, info, popup, aside, etc.) 1245 * @param string $title A title to put in the header 1246 * @param string $body Content of the module 1247 * @param array $vars Additional parameters for the module 1248 * 1249 * @return string 1250 * @since 1.8.0 1251 */ 1252function elgg_view_module($type, $title, $body, array $vars = []) { 1253 $vars['type'] = $type; 1254 $vars['title'] = $title; 1255 $vars['body'] = $body; 1256 return elgg_view('page/components/module', $vars); 1257} 1258 1259/** 1260 * Wrapper function for the message display pattern. 1261 * 1262 * Box with header, body 1263 * 1264 * This is a shortcut for {@elgg_view page/components/message}. 1265 * 1266 * @param string $type The type of message (error, success, warning, help, notice) 1267 * @param string $body Content of the message 1268 * @param array $vars Additional parameters for the message 1269 * 1270 * @return string 1271 * @since 3.0.0 1272 */ 1273function elgg_view_message($type, $body, array $vars = []) { 1274 $vars['type'] = $type; 1275 $vars['body'] = $body; 1276 return elgg_view('page/components/message', $vars); 1277} 1278 1279/** 1280 * Renders a human-readable representation of a river item 1281 * 1282 * @param \ElggRiverItem $item A river item object 1283 * @param array $vars An array of variables for the view 1284 * 'item_view' Alternative view to render the item 1285 * 'register_rss_link' Register the rss link availability (default: false) 1286 * @return string returns empty string if could not be rendered 1287 */ 1288function elgg_view_river_item($item, array $vars = []) { 1289 1290 if (!($item instanceof \ElggRiverItem)) { 1291 return ''; 1292 } 1293 1294 // checking default viewtype since some viewtypes do not have unique views per item (rss) 1295 $view = $item->getView(); 1296 1297 $subject = $item->getSubjectEntity(); 1298 $object = $item->getObjectEntity(); 1299 if (!$subject || !$object) { 1300 // subject is disabled or subject/object deleted 1301 return ''; 1302 } 1303 1304 if (elgg_extract('register_rss_link', $vars)) { 1305 elgg_register_rss_link(); 1306 } 1307 1308 $vars['item'] = $item; 1309 1310 // create river view logic 1311 $type = $object->getType(); 1312 $subtype = $object->getSubtype(); 1313 $action = $item->action_type; 1314 1315 $river_views = [ 1316 elgg_extract('item_view', $vars, ''), 1317 'river/item', // important for other viewtypes, e.g. "rss" 1318 $view, 1319 "river/{$type}/{$subtype}/{$action}", 1320 "river/{$type}/{$subtype}/default", 1321 "river/{$type}/{$action}", 1322 "river/{$type}/default", 1323 'river/elements/layout', 1324 ]; 1325 1326 $contents = ''; 1327 foreach ($river_views as $view) { 1328 if (elgg_view_exists($view)) { 1329 $contents = elgg_view($view, $vars); 1330 break; 1331 } 1332 } 1333 1334 return $contents; 1335} 1336 1337/** 1338 * Convenience function for generating a form from a view in a standard location. 1339 * 1340 * This function assumes that the body of the form is located at "forms/$action" and 1341 * sets the action by default to "action/$action". Automatically wraps the forms/$action 1342 * view with a <form> tag and inserts the anti-csrf security tokens. 1343 * 1344 * @tip This automatically appends elgg-form-action-name to the form's class. It replaces any 1345 * slashes with dashes (blog/save becomes elgg-form-blog-save) 1346 * 1347 * @example 1348 * <code>echo elgg_view_form('login');</code> 1349 * 1350 * This would assume a "login" form body to be at "forms/login" and would set the action 1351 * of the form to "http://yoursite.com/action/login". 1352 * 1353 * If elgg_view('forms/login') is: 1354 * <input type="text" name="username" /> 1355 * <input type="password" name="password" /> 1356 * 1357 * Then elgg_view_form('login') generates: 1358 * <form action="http://yoursite.com/action/login" method="post"> 1359 * ...security tokens... 1360 * <input type="text" name="username" /> 1361 * <input type="password" name="password" /> 1362 * </form> 1363 * 1364 * @param string $action The name of the action. An action name does not include 1365 * the leading "action/". For example, "login" is an action name. 1366 * @param array $form_vars $vars environment passed to the "input/form" view 1367 * - 'ajax' bool If true, the form will be submitted with an Ajax request 1368 * @param array $body_vars $vars environment passed to the "forms/$action" view 1369 * 1370 * @return string The complete form 1371 */ 1372function elgg_view_form($action, $form_vars = [], $body_vars = []) { 1373 return _elgg_services()->forms->render($action, $form_vars, $body_vars); 1374} 1375 1376/** 1377 * Sets form footer and defers its rendering until the form view and extensions have been rendered. 1378 * Deferring footer rendering allows plugins to extend the form view while maintaining 1379 * logical DOM structure. 1380 * Footer will be rendered using 'elements/forms/footer' view after form body has finished rendering 1381 * 1382 * @param string $footer Footer 1383 * @return bool 1384 */ 1385function elgg_set_form_footer($footer = '') { 1386 return _elgg_services()->forms->setFooter($footer); 1387} 1388 1389/** 1390 * Returns currently set footer, or false if not in the form rendering stack 1391 * @return string|false 1392 */ 1393function elgg_get_form_footer() { 1394 return _elgg_services()->forms->getFooter(); 1395} 1396 1397/** 1398 * Split array of vars into subarrays based on property prefixes 1399 * 1400 * @see elgg_view_field() 1401 * 1402 * @param array $vars Vars to split 1403 * @param array $prefixes Prefixes to split 1404 * 1405 * @return array 1406 */ 1407function _elgg_split_vars(array $vars = [], array $prefixes = null) { 1408 1409 if (!isset($prefixes)) { 1410 $prefixes = ['#']; 1411 } 1412 1413 $return = []; 1414 1415 foreach ($vars as $key => $value) { 1416 foreach ($prefixes as $prefix) { 1417 if (substr($key, 0, 1) === $prefix) { 1418 $key = substr($key, 1); 1419 $return[$prefix][$key] = $value; 1420 break; 1421 } else { 1422 $return[''][$key] = $value; 1423 } 1424 } 1425 } 1426 1427 return $return; 1428} 1429 1430/** 1431 * Renders a form field, usually with a wrapper element, a label, help text, etc. 1432 * 1433 * @param array $params Field parameters and variables for the input view. 1434 * Keys not prefixed with hash (#) are passed to the input view as $vars. 1435 * Keys prefixed with a hash specify the field wrapper (.elgg-view-field) output. 1436 * - #type: specifies input view. E.g. "text" uses the view "input/text". 1437 * - #label: field label HTML 1438 * - #help: field help HTML 1439 * - #class: field class name 1440 * - #view: custom view to use to render the field 1441 * - #html: can be used to render custom HTML instead of in put field, helpful when you need to add a help paragraph or similar 1442 * Note: Both #label and #help are printed unescaped within their wrapper element. 1443 * Note: Some fields (like input/checkbox) need special attention because #label and label serve different purposes 1444 * "#label" will be used as a label in the field wrapper but "label" will be used in the input view 1445 * 1446 * @return string 1447 * @since 2.3 1448 */ 1449function elgg_view_field(array $params = []) { 1450 1451 if (!empty($params['#html'])) { 1452 return $params['#html']; 1453 } 1454 1455 if (empty($params['#type'])) { 1456 _elgg_services()->logger->error(__FUNCTION__ . '(): $params["#type"] is required.'); 1457 return ''; 1458 } 1459 1460 $input_type = $params['#type']; 1461 if (!elgg_view_exists("input/$input_type")) { 1462 return ''; 1463 } 1464 1465 $hidden_types = ['hidden', 'securitytoken']; 1466 if (in_array($input_type, $hidden_types)) { 1467 $params = _elgg_split_vars($params); 1468 return elgg_view("input/$input_type", $params['']); 1469 } 1470 1471 $id = elgg_extract('id', $params); 1472 if (!$id) { 1473 $id = "elgg-field-" . base_convert(mt_rand(), 10, 36); 1474 $params['id'] = $id; 1475 } 1476 1477 $make_special_checkbox_label = false; 1478 if ($input_type == 'checkbox' && (isset($params['label']) || isset($params['#label']))) { 1479 if (isset($params['#label']) && isset($params['label'])) { 1480 $params['label_tag'] = 'div'; 1481 } else { 1482 $label = elgg_extract('label', $params); 1483 $label = elgg_extract('#label', $params, $label); 1484 1485 $params['#label'] = $label; 1486 unset($params['label']); 1487 1488 // Single checkbox input view gets special treatment 1489 // We don't want the field label to appear a checkbox without a label 1490 $make_special_checkbox_label = true; 1491 } 1492 } 1493 1494 // Need to set defaults to prevent input keys with same name ending up in element vars if not provided 1495 $defaults = [ 1496 '#class' => ELGG_ENTITIES_ANY_VALUE, 1497 '#help' => ELGG_ENTITIES_ANY_VALUE, 1498 '#label' => ELGG_ENTITIES_ANY_VALUE, 1499 '#view' => ELGG_ENTITIES_ANY_VALUE, 1500 ]; 1501 $params = array_merge($defaults, $params); 1502 1503 // first pass non-hash keys into both 1504 $split_params = _elgg_split_vars($params); 1505 1506 // $vars passed to input/$input_name 1507 $input_vars = $split_params['']; 1508 1509 // $vars passed to label, help and field wrapper views 1510 $element_vars = array_merge($split_params[''], $split_params['#']); 1511 1512 // field input view needs this 1513 $input_vars['input_type'] = $input_type; 1514 1515 // field views get more data 1516 $element_vars['input_type'] = $input_type; 1517 1518 // wrap if present 1519 $element_vars['label'] = elgg_view('elements/forms/label', $element_vars); 1520 $element_vars['help'] = elgg_view('elements/forms/help', $element_vars); 1521 1522 if ($make_special_checkbox_label) { 1523 $input_vars['label'] = $element_vars['label']; 1524 $input_vars['label_tag'] = 'div'; 1525 unset($element_vars['label']); 1526 } 1527 $element_vars['input'] = elgg_view("elements/forms/input", $input_vars); 1528 1529 return elgg_view('elements/forms/field', $element_vars); 1530} 1531 1532/** 1533 * Create a tagcloud for viewing 1534 * 1535 * 1536 * @param array $options Any elgg_get_tags() options except: 1537 * 1538 * type => must be single entity type 1539 * 1540 * subtype => must be single entity subtype 1541 * 1542 * @return string 1543 * 1544 * @see elgg_get_tags() 1545 * @since 1.7.1 1546 */ 1547function elgg_view_tagcloud(array $options = []) { 1548 1549 $type = $subtype = ''; 1550 if (isset($options['type'])) { 1551 $type = $options['type']; 1552 } 1553 if (isset($options['subtype'])) { 1554 $subtype = $options['subtype']; 1555 } 1556 1557 $tag_data = elgg_get_tags($options); 1558 return elgg_view("output/tagcloud", [ 1559 'value' => $tag_data, 1560 'type' => $type, 1561 'subtype' => $subtype, 1562 ]); 1563} 1564 1565/** 1566 * View an item in a list 1567 * 1568 * @param mixed $item Entity, annotation, river item, or other data 1569 * @param array $vars Additional parameters for the rendering 1570 * 'item_view' - Alternative view used to render list items 1571 * This parameter is required if rendering 1572 * list items that are not entity, annotation or river 1573 * @return false|string 1574 * @since 1.8.0 1575 * @internal 1576 */ 1577function elgg_view_list_item($item, array $vars = []) { 1578 1579 if ($item instanceof \ElggEntity) { 1580 return elgg_view_entity($item, $vars); 1581 } else if ($item instanceof \ElggAnnotation) { 1582 return elgg_view_annotation($item, $vars); 1583 } else if ($item instanceof \ElggRiverItem) { 1584 return elgg_view_river_item($item, $vars); 1585 } else if ($item instanceof ElggRelationship) { 1586 return elgg_view_relationship($item, $vars); 1587 } 1588 1589 $view = elgg_extract('item_view', $vars); 1590 if ($view && elgg_view_exists($view)) { 1591 $vars['item'] = $item; 1592 return elgg_view($view, $vars); 1593 } 1594 1595 return ''; 1596} 1597 1598/** 1599 * View an icon glyph 1600 * 1601 * @param string $name The specific icon to display 1602 * @param mixed $vars The additional classname as a string ('float', 'float-alt' or a custom class) 1603 * or an array of variables (array('class' => 'float')) to pass to the icon view. 1604 * 1605 * @return string The html for displaying an icon 1606 * @throws InvalidArgumentException 1607 */ 1608function elgg_view_icon($name, $vars = []) { 1609 if (empty($vars)) { 1610 $vars = []; 1611 } 1612 1613 if (is_string($vars)) { 1614 $vars = ['class' => $vars]; 1615 } 1616 1617 if (!is_array($vars)) { 1618 throw new \InvalidArgumentException('$vars needs to be a string or an array'); 1619 } 1620 1621 $vars['class'] = elgg_extract_class($vars, "elgg-icon-$name"); 1622 1623 return elgg_view("output/icon", $vars); 1624} 1625 1626/** 1627 * Include the RSS icon link and link element in the head 1628 * 1629 * @return void 1630 */ 1631function elgg_register_rss_link() { 1632 _elgg_config()->_elgg_autofeed = true; 1633} 1634 1635/** 1636 * Remove the RSS icon link and link element from the head 1637 * 1638 * @return void 1639 */ 1640function elgg_unregister_rss_link() { 1641 _elgg_config()->_elgg_autofeed = false; 1642} 1643 1644/** 1645 * Should the RSS view of this URL be linked to? 1646 * 1647 * @return bool 1648 * @internal 1649 */ 1650function _elgg_has_rss_link() { 1651 if (_elgg_config()->disable_rss) { 1652 return false; 1653 } 1654 1655 return (bool) _elgg_config()->_elgg_autofeed; 1656} 1657 1658/** 1659 * Minifies simplecache CSS and JS views by handling the "simplecache:generate" hook 1660 * 1661 * @param \Elgg\Hook $hook 'simplecache:generate', 'css' 1662 * 1663 * @return string|null View content minified (if css/js type) 1664 * @internal 1665 */ 1666function _elgg_views_minify(\Elgg\Hook $hook) { 1667 if (preg_match('~[\.-]min\.~', $hook->getParam('view'))) { 1668 // bypass minification 1669 return; 1670 } 1671 1672 $content = $hook->getValue(); 1673 1674 if ($hook->getType() === 'js') { 1675 if (_elgg_config()->simplecache_minify_js) { 1676 return JSMin::minify($content); 1677 } 1678 } elseif ($hook->getType() === 'css') { 1679 if (_elgg_config()->simplecache_minify_css) { 1680 $cssmin = new CSSmin(); 1681 return $cssmin->run($content); 1682 } 1683 } 1684} 1685 1686/** 1687 * Preprocesses CSS views sent by /cache URLs 1688 * 1689 * @param \Elgg\Hook $hook 'cache:generate' | 'simplecache:generate', 'css' 1690 * 1691 * @return string|null View content 1692 * @internal 1693 */ 1694function _elgg_views_preprocess_css(\Elgg\Hook $hook) { 1695 $options = $hook->getParam('compiler_options', []); 1696 return _elgg_services()->cssCompiler->compile($hook->getValue(), $options); 1697} 1698 1699/** 1700 * Inserts module names into anonymous modules by handling the "simplecache:generate" and "cache:generate" hook. 1701 * 1702 * @param \Elgg\Hook $hook 'cache:generate' | 'simplecache:generate', 'js' 1703 * 1704 * @return string|null View content minified (if css/js type) 1705 * @internal 1706 */ 1707function _elgg_views_amd(\Elgg\Hook $hook) { 1708 $filter = new \Elgg\Amd\ViewFilter(); 1709 return $filter->filter($hook->getParam('view'), $hook->getValue()); 1710} 1711 1712/** 1713 * Sends X-Frame-Options header on page requests 1714 * 1715 * @return void 1716 * 1717 * @internal 1718 */ 1719function _elgg_views_send_header_x_frame_options() { 1720 elgg_set_http_header('X-Frame-Options: SAMEORIGIN'); 1721} 1722 1723/** 1724 * Initialize viewtypes on system boot event 1725 * This ensures simplecache is cleared during upgrades. See #2252 1726 * 1727 * @return void 1728 * @internal 1729 * @elgg_event_handler boot system 1730 */ 1731function elgg_views_boot() { 1732 _elgg_services()->viewCacher->registerCoreViews(); 1733 1734 // jQuery and UI must come before require. See #9024 1735 elgg_register_external_file('js', 'jquery', elgg_get_simplecache_url('jquery.js'), 'head'); 1736 elgg_load_external_file('js', 'jquery'); 1737 1738 elgg_register_external_file('js', 'jquery-ui', elgg_get_simplecache_url('jquery-ui.js'), 'head'); 1739 elgg_load_external_file('js', 'jquery-ui'); 1740 1741 elgg_register_external_file('js', 'elgg.require_config', elgg_get_simplecache_url('elgg/require_config.js'), 'head'); 1742 elgg_load_external_file('js', 'elgg.require_config'); 1743 1744 elgg_register_external_file('js', 'require', elgg_get_simplecache_url('require.js'), 'head'); 1745 elgg_load_external_file('js', 'require'); 1746 1747 elgg_register_external_file('js', 'elgg', elgg_get_simplecache_url('elgg.js'), 'head'); 1748 elgg_load_external_file('js', 'elgg'); 1749 1750 elgg_register_external_file('css', 'font-awesome', elgg_get_simplecache_url('font-awesome/css/all.min.css')); 1751 elgg_load_external_file('css', 'font-awesome'); 1752 1753 elgg_define_js('cropperjs', [ 1754 'src' => elgg_get_simplecache_url('cropperjs/cropper.min.js'), 1755 ]); 1756 elgg_define_js('jquery-cropper/jquery-cropper', [ 1757 'src' => elgg_get_simplecache_url('jquery-cropper/jquery-cropper.min.js'), 1758 ]); 1759 1760 elgg_require_css('elgg'); 1761 1762 elgg_register_simplecache_view('elgg/init.js'); 1763 1764 elgg_extend_view('initialize_elgg.js', 'elgg/prevent_clicks.js', 1); 1765 1766 elgg_extend_view('elgg.css', 'lightbox/elgg-colorbox-theme/colorbox.css'); 1767 elgg_extend_view('elgg.css', 'entity/edit/icon/crop.css'); 1768 1769 elgg_define_js('jquery.ui.autocomplete.html', [ 1770 'deps' => ['jquery-ui'], 1771 ]); 1772 1773 // @deprecated 3.1 1774 elgg_register_external_file('js', 'elgg.avatar_cropper', elgg_get_simplecache_url('elgg/ui.avatar_cropper.js')); 1775 1776 // @deprecated 2.2 1777 elgg_register_external_file('js', 'elgg.ui.river', elgg_get_simplecache_url('elgg/ui.river.js')); 1778 1779 // @deprecated 3.1 no longer use imageareaselect js and css 1780 elgg_register_external_file('js', 'jquery.imgareaselect', elgg_get_simplecache_url('jquery.imgareaselect.js')); 1781 elgg_register_external_file('css', 'jquery.imgareaselect', elgg_get_simplecache_url('jquery.imgareaselect.css')); 1782 1783 // @deprecated 3.1 no longer use treeview js and css 1784 elgg_register_external_file('css', 'jquery.treeview', elgg_get_simplecache_url('jquery-treeview/jquery.treeview.css')); 1785 elgg_define_js('jquery.treeview', [ 1786 'src' => elgg_get_simplecache_url('jquery-treeview/jquery.treeview.js'), 1787 'exports' => 'jQuery.fn.treeview', 1788 'deps' => ['jquery'], 1789 ]); 1790 1791 elgg_register_ajax_view('languages.js'); 1792 1793 // pre-process CSS regardless of simplecache 1794 elgg_register_plugin_hook_handler('cache:generate', 'css', '_elgg_views_preprocess_css'); 1795 elgg_register_plugin_hook_handler('simplecache:generate', 'css', '_elgg_views_preprocess_css'); 1796 1797 elgg_register_plugin_hook_handler('simplecache:generate', 'js', '_elgg_views_amd'); 1798 elgg_register_plugin_hook_handler('cache:generate', 'js', '_elgg_views_amd'); 1799 elgg_register_plugin_hook_handler('simplecache:generate', 'css', '_elgg_views_minify'); 1800 elgg_register_plugin_hook_handler('simplecache:generate', 'js', '_elgg_views_minify'); 1801 1802 elgg_register_plugin_hook_handler('output:before', 'page', '_elgg_views_send_header_x_frame_options'); 1803 1804 elgg_register_plugin_hook_handler('view_vars', 'elements/forms/help', '_elgg_views_file_help_upload_limit'); 1805 1806 // registered with high priority for BC 1807 // prior to 2.2 registration used to take place in _elgg_views_prepare_head() before the hook was triggered 1808 elgg_register_plugin_hook_handler('head', 'page', '_elgg_views_prepare_favicon_links', 1); 1809 1810 // set default icon sizes - can be overridden with plugin 1811 if (!_elgg_config()->hasValue('icon_sizes')) { 1812 $icon_sizes = [ 1813 'topbar' => ['w' => 16, 'h' => 16, 'square' => true, 'upscale' => true], 1814 'tiny' => ['w' => 25, 'h' => 25, 'square' => true, 'upscale' => true], 1815 'small' => ['w' => 40, 'h' => 40, 'square' => true, 'upscale' => true], 1816 'medium' => ['w' => 100, 'h' => 100, 'square' => true, 'upscale' => true], 1817 'large' => ['w' => 200, 'h' => 200, 'square' => true, 'upscale' => true], 1818 'master' => ['w' => 10240, 'h' => 10240, 'square' => false, 'upscale' => false, 'crop' => false], 1819 ]; 1820 elgg_set_config('icon_sizes', $icon_sizes); 1821 } 1822 1823 // Configure lightbox 1824 elgg_register_plugin_hook_handler('elgg.data', 'site', '_elgg_set_lightbox_config'); 1825} 1826 1827/** 1828 * Get the site data to be merged into "elgg" in elgg.js. 1829 * 1830 * Unlike _elgg_get_js_page_data(), the keys returned are literal expressions. 1831 * 1832 * @return array 1833 * @internal 1834 */ 1835function _elgg_get_js_site_data() { 1836 $language = _elgg_config()->language; 1837 if (!$language) { 1838 $language = 'en'; 1839 } 1840 1841 return [ 1842 'elgg.data' => (object) elgg_trigger_plugin_hook('elgg.data', 'site', null, []), 1843 'elgg.version' => elgg_get_version(), 1844 'elgg.release' => elgg_get_version(true), 1845 'elgg.config.wwwroot' => elgg_get_site_url(), 1846 1847 // refresh token 3 times during its lifetime (in microseconds 1000 * 1/3) 1848 'elgg.security.interval' => (int) elgg()->csrf->getActionTokenTimeout() * 333, 1849 'elgg.config.language' => $language, 1850 ]; 1851} 1852 1853/** 1854 * Get the initial contents of "elgg" client side. Will be extended by elgg.js. 1855 * 1856 * @return array 1857 * @internal 1858 */ 1859function _elgg_get_js_page_data() { 1860 $data = elgg_trigger_plugin_hook('elgg.data', 'page', null, []); 1861 if (!is_array($data)) { 1862 elgg_log('"elgg.data" plugin hook handlers must return an array. Returned ' . gettype($data) . '.', 'ERROR'); 1863 $data = []; 1864 } 1865 1866 $elgg = [ 1867 'config' => [ 1868 'lastcache' => (int) _elgg_config()->lastcache, 1869 'viewtype' => elgg_get_viewtype(), 1870 'simplecache_enabled' => (int) elgg_is_simplecache_enabled(), 1871 'current_language' => get_current_language(), 1872 ], 1873 'security' => [ 1874 'token' => [ 1875 '__elgg_ts' => $ts = elgg()->csrf->getCurrentTime()->getTimestamp(), 1876 '__elgg_token' => elgg()->csrf->generateActionToken($ts), 1877 ], 1878 ], 1879 'session' => [ 1880 'user' => null, 1881 'token' => _elgg_services()->session->get('__elgg_session'), 1882 ], 1883 '_data' => (object) $data, 1884 ]; 1885 1886 if (_elgg_config()->elgg_load_sync_code) { 1887 $elgg['config']['load_sync_code'] = true; 1888 } 1889 1890 $page_owner = elgg_get_page_owner_entity(); 1891 if ($page_owner instanceof ElggEntity) { 1892 $elgg['page_owner'] = $page_owner->toObject(); 1893 } 1894 1895 $user = elgg_get_logged_in_user_entity(); 1896 if ($user instanceof ElggUser) { 1897 $user_object = $user->toObject(); 1898 $user_object->admin = $user->isAdmin(); 1899 $elgg['session']['user'] = $user_object; 1900 } 1901 1902 return $elgg; 1903} 1904 1905/** 1906 * Render a view while the global viewtype is temporarily changed. This makes sure that 1907 * nested views use the same viewtype. 1908 * 1909 * @param string $view View name 1910 * @param array $vars View vars 1911 * @param string $viewtype Temporary viewtype ('' to leave current) 1912 * 1913 * @return mixed 1914 * @internal 1915 */ 1916function _elgg_view_under_viewtype($view, $vars, $viewtype) { 1917 $current_view_type = null; 1918 if ($viewtype) { 1919 $current_view_type = elgg_get_viewtype(); 1920 elgg_set_viewtype($viewtype); 1921 } 1922 1923 $ret = elgg_view($view, $vars); 1924 1925 if (isset($current_view_type)) { 1926 elgg_set_viewtype($current_view_type); 1927 } 1928 1929 return $ret; 1930} 1931 1932/** 1933 * Set lightbox config 1934 * 1935 * @param \Elgg\Hook $hook "elgg.data", "site" 1936 * 1937 * @return array 1938 * @internal 1939 */ 1940function _elgg_set_lightbox_config(\Elgg\Hook $hook) { 1941 $return = $hook->getValue(); 1942 1943 $return['lightbox'] = [ 1944 'current' => elgg_echo('js:lightbox:current', ['{current}', '{total}']), 1945 'previous' => elgg_view_icon('caret-left'), 1946 'next' => elgg_view_icon('caret-right'), 1947 'close' => elgg_view_icon('times'), 1948 'opacity' => 0.5, 1949 'maxWidth' => '990px', 1950 'maxHeight' => '990px', 1951 'initialWidth' => '300px', 1952 'initialHeight' => '300px', 1953 ]; 1954 1955 return $return; 1956} 1957 1958/** 1959 * Add a help text to input/file about upload limit 1960 * 1961 * In order to not show the help text supply 'show_upload_limit' => false to elgg_view_field() 1962 * 1963 * @param \Elgg\Hook $hook 'view_vars' 'elements/forms/help' 1964 * 1965 * @return void|array 1966 * @internal 1967 */ 1968function _elgg_views_file_help_upload_limit(\Elgg\Hook $hook) { 1969 1970 $return = $hook->getValue(); 1971 if (elgg_extract('input_type', $return) !== 'file') { 1972 return; 1973 } 1974 1975 if (!elgg_extract('show_upload_limit', $return, true)) { 1976 return; 1977 } 1978 1979 $help = elgg_extract('help', $return, ''); 1980 1981 // Get post_max_size and upload_max_filesize 1982 $post_max_size = elgg_get_ini_setting_in_bytes('post_max_size'); 1983 $upload_max_filesize = elgg_get_ini_setting_in_bytes('upload_max_filesize'); 1984 1985 // Determine the correct value 1986 $max_upload = $upload_max_filesize > $post_max_size ? $post_max_size : $upload_max_filesize; 1987 1988 $help .= ' ' . elgg_echo('input:file:upload_limit', [elgg_format_bytes($max_upload)]); 1989 1990 $return['help'] = trim($help); 1991 1992 return $return; 1993} 1994 1995/** 1996 * Maps legacy sprite classes and FontAwesome 4 classes to FontAwesome 5 classes 1997 * 1998 * @param array $classes Icon classes 1999 * @param bool $map_sprites Map legacy Elgg sprites 2000 * 2001 * @return array 2002 * @internal 2003 */ 2004function _elgg_map_icon_glyph_class(array $classes, $map_sprites = true) { 2005 2006 // these 'old' Elgg 1.x sprite icons will be converted to the FontAwesome version 2007 $legacy_sprites = [ 2008 "arrow-two-head" => "arrows-h", 2009 "attention" => "exclamation-triangle", 2010 "cell-phone" => "mobile", 2011 "checkmark" => "check", 2012 "clip" => "paperclip", 2013 "cursor-drag-arrow" => "arrows", 2014 "drag-arrow" => "arrows", // 'old' admin sprite 2015 "delete-alt" => "times-circle", 2016 "delete" => "times", 2017 "facebook" => "facebook-square", 2018 "grid" => "th", 2019 "hover-menu" => "caret-down", 2020 "info" => "info-circle", 2021 "lock-closed" => "lock", 2022 "lock-open" => "unlock", 2023 "mail" => "envelope-o", 2024 "mail-alt" => "envelope", 2025 "print-alt" => "print", 2026 "push-pin" => "thumb-tack", 2027 "push-pin-alt" => "thumb-tack", 2028 "redo" => "share", 2029 "round-arrow-left" => "arrow-circle-left", 2030 "round-arrow-right" => "arrow-circle-right", 2031 "round-checkmark" => "check-circle", 2032 "round-minus" => "minus-circle", 2033 "round-plus" => "plus-circle", 2034 "rss" => "rss-square", 2035 "search-focus" => "search", 2036 "settings" => "wrench", 2037 "settings-alt" => "cog", 2038 "share" => "share-alt-square", 2039 "shop-cart" => "shopping-cart", 2040 "speech-bubble" => "comment", 2041 "speech-bubble-alt" => "comments", 2042 "star-alt" => "star", 2043 "star-empty" => "star-o", 2044 "thumbs-down-alt" => "thumbs-down", 2045 "thumbs-up-alt" => "thumbs-up", 2046 "trash" => "trash-o", 2047 "twitter" => "twitter-square", 2048 "undo" => "reply", 2049 "video" => "film" 2050 ]; 2051 2052 $fa5 = [ 2053 'address-book-o' => ['address-book', 'far'], 2054 'address-card-o' => ['address-card', 'far'], 2055 'area-chart' => ['chart-area', 'fas'], 2056 'arrow-circle-o-down' => ['arrow-alt-circle-down', 'far'], 2057 'arrow-circle-o-left' => ['arrow-alt-circle-left', 'far'], 2058 'arrow-circle-o-right' => ['arrow-alt-circle-right', 'far'], 2059 'arrow-circle-o-up' => ['arrow-alt-circle-up', 'far'], 2060 'arrows-alt' => ['expand-arrows-alt', 'fas'], 2061 'arrows-h' => ['arrows-alt-h', 'fas'], 2062 'arrows-v' => ['arrows-alt-v', 'fas'], 2063 'arrows' => ['arrows-alt', 'fas'], 2064 'asl-interpreting' => ['american-sign-language-interpreting', 'fas'], 2065 'automobile' => ['car', 'fas'], 2066 'bank' => ['university', 'fas'], 2067 'bar-chart-o' => ['chart-bar', 'far'], 2068 'bar-chart' => ['chart-bar', 'far'], 2069 'bathtub' => ['bath', 'fas'], 2070 'battery-0' => ['battery-empty', 'fas'], 2071 'battery-1' => ['battery-quarter', 'fas'], 2072 'battery-2' => ['battery-half', 'fas'], 2073 'battery-3' => ['battery-three-quarters', 'fas'], 2074 'battery-4' => ['battery-full', 'fas'], 2075 'battery' => ['battery-full', 'fas'], 2076 'bell-o' => ['bell', 'far'], 2077 'bell-slash-o' => ['bell-slash', 'far'], 2078 'bitbucket-square' => ['bitbucket', 'fab'], 2079 'bitcoin' => ['btc', 'fab'], 2080 'bookmark-o' => ['bookmark', 'far'], 2081 'building-o' => ['building', 'far'], 2082 'cab' => ['taxi', 'fas'], 2083 'calendar-check-o' => ['calendar-check', 'far'], 2084 'calendar-minus-o' => ['calendar-minus', 'far'], 2085 'calendar-o' => ['calendar', 'far'], 2086 'calendar-plus-o' => ['calendar-plus', 'far'], 2087 'calendar-times-o' => ['calendar-times', 'far'], 2088 'calendar' => ['calendar-alt', 'fas'], 2089 'caret-square-o-down' => ['caret-square-down', 'far'], 2090 'caret-square-o-left' => ['caret-square-left', 'far'], 2091 'caret-square-o-right' => ['caret-square-right', 'far'], 2092 'caret-square-o-up' => ['caret-square-up', 'far'], 2093 'cc' => ['closed-captioning', 'far'], 2094 'chain-broken' => ['unlink', 'fas'], 2095 'chain' => ['link', 'fas'], 2096 'check-circle-o' => ['check-circle', 'far'], 2097 'check-square-o' => ['check-square', 'far'], 2098 'circle-o-notch' => ['circle-notch', 'fas'], 2099 'circle-o' => ['circle', 'far'], 2100 'circle-thin' => ['circle', 'far'], 2101 'clock-o' => ['clock', 'far'], 2102 'close' => ['times', 'fas'], 2103 'cloud-download' => ['cloud-download-alt', 'fas'], 2104 'cloud-upload' => ['cloud-upload-alt', 'fas'], 2105 'cny' => ['yen-sign', 'fas'], 2106 'code-fork' => ['code-branch', 'fas'], 2107 'comment-o' => ['comment', 'far'], 2108 'commenting-o' => ['comment-alt', 'far'], 2109 'commenting' => ['comment-alt', 'fas'], 2110 'comments-o' => ['comments', 'far'], 2111 'credit-card-alt' => ['credit-card', 'fas'], 2112 'cutlery' => ['utensils', 'fas'], 2113 'dashboard' => ['tachometer-alt', 'fas'], 2114 'deafness' => ['deaf', 'fas'], 2115 'dedent' => ['outdent', 'fas'], 2116 'diamond' => ['gem', 'far'], 2117 'dollar' => ['dollar-sign', 'fas'], 2118 'dot-circle-o' => ['dot-circle', 'far'], 2119 'drivers-license-o' => ['id-card', 'far'], 2120 'drivers-license' => ['id-card', 'fas'], 2121 'eercast' => ['sellcast', 'fab'], 2122 'envelope-o' => ['envelope', 'far'], 2123 'envelope-open-o' => ['envelope-open', 'far'], 2124 'eur' => ['euro-sign', 'fas'], 2125 'euro' => ['euro-sign', 'fas'], 2126 'exchange' => ['exchange-alt', 'fas'], 2127 'external-link-square' => ['external-link-square-alt', 'fas'], 2128 'external-link' => ['external-link-alt', 'fas'], 2129 'eyedropper' => ['eye-dropper', 'fas'], 2130 'fa' => ['font-awesome', 'fab'], 2131 'facebook-f' => ['facebook-f', 'fab'], 2132 'facebook-official' => ['facebook', 'fab'], 2133 'facebook' => ['facebook-f', 'fab'], 2134 'feed' => ['rss', 'fas'], 2135 'file-archive-o' => ['file-archive', 'far'], 2136 'file-audio-o' => ['file-audio', 'far'], 2137 'file-code-o' => ['file-code', 'far'], 2138 'file-excel-o' => ['file-excel', 'far'], 2139 'file-image-o' => ['file-image', 'far'], 2140 'file-movie-o' => ['file-video', 'far'], 2141 'file-o' => ['file', 'far'], 2142 'file-pdf-o' => ['file-pdf', 'far'], 2143 'file-photo-o' => ['file-image', 'far'], 2144 'file-picture-o' => ['file-image', 'far'], 2145 'file-powerpoint-o' => ['file-powerpoint', 'far'], 2146 'file-sound-o' => ['file-audio', 'far'], 2147 'file-text-o' => ['file-alt', 'far'], 2148 'file-text' => ['file-alt', 'fas'], 2149 'file-video-o' => ['file-video', 'far'], 2150 'file-word-o' => ['file-word', 'far'], 2151 'file-zip-o' => ['file-archive', 'far'], 2152 'files-o' => ['copy', 'far'], 2153 'flag-o' => ['flag', 'far'], 2154 'flash' => ['bolt', 'fas'], 2155 'floppy-o' => ['save', 'far'], 2156 'folder-o' => ['folder', 'far'], 2157 'folder-open-o' => ['folder-open', 'far'], 2158 'frown-o' => ['frown', 'far'], 2159 'futbol-o' => ['futbol', 'far'], 2160 'gbp' => ['pound-sign', 'fas'], 2161 'ge' => ['empire', 'fab'], 2162 'gear' => ['cog', 'fas'], 2163 'gears' => ['cogs', 'fas'], 2164 'gittip' => ['gratipay', 'fab'], 2165 'glass' => ['glass-martini', 'fas'], 2166 'google-plus-circle' => ['google-plus', 'fab'], 2167 'google-plus-official' => ['google-plus', 'fab'], 2168 'google-plus' => ['google-plus-g', 'fab'], 2169 'group' => ['users', 'fas'], 2170 'hand-grab-o' => ['hand-rock', 'far'], 2171 'hand-lizard-o' => ['hand-lizard', 'far'], 2172 'hand-o-down' => ['hand-point-down', 'far'], 2173 'hand-o-left' => ['hand-point-left', 'far'], 2174 'hand-o-right' => ['hand-point-right', 'far'], 2175 'hand-o-up' => ['hand-point-up', 'far'], 2176 'hand-paper-o' => ['hand-paper', 'far'], 2177 'hand-peace-o' => ['hand-peace', 'far'], 2178 'hand-pointer-o' => ['hand-pointer', 'far'], 2179 'hand-rock-o' => ['hand-rock', 'far'], 2180 'hand-scissors-o' => ['hand-scissors', 'far'], 2181 'hand-spock-o' => ['hand-spock', 'far'], 2182 'hand-stop-o' => ['hand-paper', 'far'], 2183 'handshake-o' => ['handshake', 'far'], 2184 'hard-of-hearing' => ['deaf', 'fas'], 2185 'hdd-o' => ['hdd', 'far'], 2186 'header' => ['heading', 'fas'], 2187 'heart-o' => ['heart', 'far'], 2188 'hospital-o' => ['hospital', 'far'], 2189 'hotel' => ['bed', 'fas'], 2190 'hourglass-1' => ['hourglass-start', 'fas'], 2191 'hourglass-2' => ['hourglass-half', 'fas'], 2192 'hourglass-3' => ['hourglass-end', 'fas'], 2193 'hourglass-o' => ['hourglass', 'far'], 2194 'id-card-o' => ['id-card', 'far'], 2195 'ils' => ['shekel-sign', 'fas'], 2196 'image' => ['image', 'far'], 2197 'inr' => ['rupee-sign', 'fas'], 2198 'institution' => ['university', 'fas'], 2199 'intersex' => ['transgender', 'fas'], 2200 'jpy' => ['yen-sign', 'fas'], 2201 'keyboard-o' => ['keyboard', 'far'], 2202 'krw' => ['won-sign', 'fas'], 2203 'legal' => ['gavel', 'fas'], 2204 'lemon-o' => ['lemon', 'far'], 2205 'level-down' => ['level-down-alt', 'fas'], 2206 'level-up' => ['level-up-alt', 'fas'], 2207 'life-bouy' => ['life-ring', 'far'], 2208 'life-buoy' => ['life-ring', 'far'], 2209 'life-saver' => ['life-ring', 'far'], 2210 'lightbulb-o' => ['lightbulb', 'far'], 2211 'line-chart' => ['chart-line', 'fas'], 2212 'linkedin-square' => ['linkedin', 'fab'], 2213 'linkedin' => ['linkedin-in', 'fab'], 2214 'long-arrow-down' => ['long-arrow-alt-down', 'fas'], 2215 'long-arrow-left' => ['long-arrow-alt-left', 'fas'], 2216 'long-arrow-right' => ['long-arrow-alt-right', 'fas'], 2217 'long-arrow-up' => ['long-arrow-alt-up', 'fas'], 2218 'mail-forward' => ['share', 'fas'], 2219 'mail-reply-all' => ['reply-all', 'fas'], 2220 'mail-reply' => ['reply', 'fas'], 2221 'map-marker' => ['map-marker-alt', 'fas'], 2222 'map-o' => ['map', 'far'], 2223 'meanpath' => ['font-awesome', 'fab'], 2224 'meh-o' => ['meh', 'far'], 2225 'minus-square-o' => ['minus-square', 'far'], 2226 'mobile-phone' => ['mobile-alt', 'fas'], 2227 'mobile' => ['mobile-alt', 'fas'], 2228 'money' => ['money-bill-alt', 'far'], 2229 'moon-o' => ['moon', 'far'], 2230 'mortar-board' => ['graduation-cap', 'fas'], 2231 'navicon' => ['bars', 'fas'], 2232 'newspaper-o' => ['newspaper', 'far'], 2233 'paper-plane-o' => ['paper-plane', 'far'], 2234 'paste' => ['clipboard', 'far'], 2235 'pause-circle-o' => ['pause-circle', 'far'], 2236 'pencil-square-o' => ['edit', 'far'], 2237 'pencil-square' => ['pen-square', 'fas'], 2238 'pencil' => ['pencil-alt', 'fas'], 2239 'photo' => ['image', 'far'], 2240 'picture-o' => ['image', 'far'], 2241 'pie-chart' => ['chart-pie', 'fas'], 2242 'play-circle-o' => ['play-circle', 'far'], 2243 'plus-square-o' => ['plus-square', 'far'], 2244 'question-circle-o' => ['question-circle', 'far'], 2245 'ra' => ['rebel', 'fab'], 2246 'refresh' => ['sync', 'fas'], 2247 'remove' => ['times', 'fas'], 2248 'reorder' => ['bars', 'fas'], 2249 'repeat' => ['redo', 'fas'], 2250 'resistance' => ['rebel', 'fab'], 2251 'rmb' => ['yen-sign', 'fas'], 2252 'rotate-left' => ['undo', 'fas'], 2253 'rotate-right' => ['redo', 'fas'], 2254 'rouble' => ['ruble-sign', 'fas'], 2255 'rub' => ['ruble-sign', 'fas'], 2256 'ruble' => ['ruble-sign', 'fas'], 2257 'rupee' => ['rupee-sign', 'fas'], 2258 's15' => ['bath', 'fas'], 2259 'scissors' => ['cut', 'fas'], 2260 'send-o' => ['paper-plane', 'far'], 2261 'send' => ['paper-plane', 'fas'], 2262 'share-square-o' => ['share-square', 'far'], 2263 'shekel' => ['shekel-sign', 'fas'], 2264 'sheqel' => ['shekel-sign', 'fas'], 2265 'shield' => ['shield-alt', 'fas'], 2266 'sign-in' => ['sign-in-alt', 'fas'], 2267 'sign-out' => ['sign-out-alt', 'fas'], 2268 'signing' => ['sign-language', 'fas'], 2269 'sliders' => ['sliders-h', 'fas'], 2270 'smile-o' => ['smile', 'far'], 2271 'snowflake-o' => ['snowflake', 'far'], 2272 'soccer-ball-o' => ['futbol', 'far'], 2273 'sort-alpha-asc' => ['sort-alpha-down', 'fas'], 2274 'sort-alpha-desc' => ['sort-alpha-up', 'fas'], 2275 'sort-amount-asc' => ['sort-amount-down', 'fas'], 2276 'sort-amount-desc' => ['sort-amount-up', 'fas'], 2277 'sort-asc' => ['sort-up', 'fas'], 2278 'sort-desc' => ['sort-down', 'fas'], 2279 'sort-numeric-asc' => ['sort-numeric-down', 'fas'], 2280 'sort-numeric-desc' => ['sort-numeric-up', 'fas'], 2281 'spoon' => ['utensil-spoon', 'fas'], 2282 'square-o' => ['square', 'far'], 2283 'star-half-empty' => ['star-half', 'far'], 2284 'star-half-full' => ['star-half', 'far'], 2285 'star-half-o' => ['star-half', 'far'], 2286 'star-o' => ['star', 'far'], 2287 'sticky-note-o' => ['sticky-note', 'far'], 2288 'stop-circle-o' => ['stop-circle', 'far'], 2289 'sun-o' => ['sun', 'far'], 2290 'support' => ['life-ring', 'far'], 2291 'tablet' => ['tablet-alt', 'fas'], 2292 'tachometer' => ['tachometer-alt', 'fas'], 2293 'television' => ['tv', 'fas'], 2294 'thermometer-0' => ['thermometer-empty', 'fas'], 2295 'thermometer-1' => ['thermometer-quarter', 'fas'], 2296 'thermometer-2' => ['thermometer-half', 'fas'], 2297 'thermometer-3' => ['thermometer-three-quarters', 'fas'], 2298 'thermometer-4' => ['thermometer-full', 'fas'], 2299 'thermometer' => ['thermometer-full', 'fas'], 2300 'thumb-tack' => ['thumbtack', 'fas'], 2301 'thumbs-o-down' => ['thumbs-down', 'far'], 2302 'thumbs-o-up' => ['thumbs-up', 'far'], 2303 'ticket' => ['ticket-alt', 'fas'], 2304 'times-circle-o' => ['times-circle', 'far'], 2305 'times-rectangle-o' => ['window-close', 'far'], 2306 'times-rectangle' => ['window-close', 'fas'], 2307 'toggle-down' => ['caret-square-down', 'far'], 2308 'toggle-left' => ['caret-square-left', 'far'], 2309 'toggle-right' => ['caret-square-right', 'far'], 2310 'toggle-up' => ['caret-square-up', 'far'], 2311 'trash-o' => ['trash-alt', 'far'], 2312 'trash' => ['trash-alt', 'fas'], 2313 'try' => ['lira-sign', 'fas'], 2314 'turkish-lira' => ['lira-sign', 'fas'], 2315 'unsorted' => ['sort', 'fas'], 2316 'usd' => ['dollar-sign', 'fas'], 2317 'user-circle-o' => ['user-circle', 'far'], 2318 'user-o' => ['user', 'far'], 2319 'vcard-o' => ['address-card', 'far'], 2320 'vcard' => ['address-card', 'fas'], 2321 'video-camera' => ['video', 'fas'], 2322 'vimeo' => ['vimeo-v', 'fab'], 2323 'volume-control-phone' => ['phone-volume', 'fas'], 2324 'warning' => ['exclamation-triangle', 'fas'], 2325 'wechat' => ['weixin', 'fab'], 2326 'wheelchair-alt' => ['accessible-icon', 'fab'], 2327 'window-close-o' => ['window-close', 'far'], 2328 'won' => ['won-sign', 'fas'], 2329 'y-combinator-square' => ['hacker-news', 'fab'], 2330 'yc-square' => ['hacker-news', 'fab'], 2331 'yc' => ['y-combinator', 'fab'], 2332 'yen' => ['yen-sign', 'fas'], 2333 'youtube-play' => ['youtube', 'fab'], 2334 'youtube-square' => ['youtube', 'fab'], 2335 ]; 2336 2337 $brands = [ 2338 '500px', 2339 'accessible-icon', 2340 'accusoft', 2341 'adn', 2342 'adversal', 2343 'affiliatetheme', 2344 'algolia', 2345 'amazon', 2346 'amazon-pay', 2347 'amilia', 2348 'android', 2349 'angellist', 2350 'angrycreative', 2351 'angular', 2352 'app-store', 2353 'app-store-ios', 2354 'apper', 2355 'apple', 2356 'apple-pay', 2357 'asymmetrik', 2358 'audible', 2359 'autoprefixer', 2360 'avianex', 2361 'aviato', 2362 'aws', 2363 'bandcamp', 2364 'behance', 2365 'behance-square', 2366 'bimobject', 2367 'bitbucket', 2368 'bitcoin', 2369 'bity', 2370 'black-tie', 2371 'blackberry', 2372 'blogger', 2373 'blogger-b', 2374 'bluetooth', 2375 'bluetooth-b', 2376 'btc', 2377 'buromobelexperte', 2378 'buysellads', 2379 'cc-amazon-pay', 2380 'cc-amex', 2381 'cc-apple-pay', 2382 'cc-diners-club', 2383 'cc-discover', 2384 'cc-jcb', 2385 'cc-mastercard', 2386 'cc-paypal', 2387 'cc-stripe', 2388 'cc-visa', 2389 'centercode', 2390 'chrome', 2391 'cloudscale', 2392 'cloudsmith', 2393 'cloudversify', 2394 'codepen', 2395 'codiepie', 2396 'connectdevelop', 2397 'contao', 2398 'cpanel', 2399 'creative-commons', 2400 'css3', 2401 'css3-alt', 2402 'cuttlefish', 2403 'd-and-d', 2404 'dashcube', 2405 'delicious', 2406 'deploydog', 2407 'deskpro', 2408 'deviantart', 2409 'digg', 2410 'digital-ocean', 2411 'discord', 2412 'discourse', 2413 'dochub', 2414 'docker', 2415 'draft2digital', 2416 'dribbble', 2417 'dribbble-square', 2418 'dropbox', 2419 'drupal', 2420 'dyalog', 2421 'earlybirds', 2422 'edge', 2423 'elementor', 2424 'ember', 2425 'empire', 2426 'envira', 2427 'erlang', 2428 'ethereum', 2429 'etsy', 2430 'expeditedssl', 2431 'facebook', 2432 'facebook-f', 2433 'facebook-messenger', 2434 'facebook-square', 2435 'firefox', 2436 'first-order', 2437 'firstdraft', 2438 'flickr', 2439 'flipboard', 2440 'fly', 2441 'font-awesome', 2442 'font-awesome-alt', 2443 'font-awesome-flag', 2444 'fonticons', 2445 'fonticons-fi', 2446 'fort-awesome', 2447 'fort-awesome-alt', 2448 'forumbee', 2449 'foursquare', 2450 'free-code-camp', 2451 'freebsd', 2452 'get-pocket', 2453 'gg', 2454 'gg-circle', 2455 'git', 2456 'git-square', 2457 'github', 2458 'github-alt', 2459 'github-square', 2460 'gitkraken', 2461 'gitlab', 2462 'gitter', 2463 'glide', 2464 'glide-g', 2465 'gofore', 2466 'goodreads', 2467 'goodreads-g', 2468 'google', 2469 'google-drive', 2470 'google-play', 2471 'google-plus', 2472 'google-plus-g', 2473 'google-plus-square', 2474 'google-wallet', 2475 'gratipay', 2476 'grav', 2477 'gripfire', 2478 'grunt', 2479 'gulp', 2480 'hacker-news', 2481 'hacker-news-square', 2482 'hips', 2483 'hire-a-helper', 2484 'hooli', 2485 'hotjar', 2486 'houzz', 2487 'html5', 2488 'hubspot', 2489 'imdb', 2490 'instagram', 2491 'internet-explorer', 2492 'ioxhost', 2493 'itunes', 2494 'itunes-note', 2495 'jenkins', 2496 'joget', 2497 'joomla', 2498 'js', 2499 'js-square', 2500 'jsfiddle', 2501 'keycdn', 2502 'kickstarter', 2503 'kickstarter-k', 2504 'korvue', 2505 'laravel', 2506 'lastfm', 2507 'lastfm-square', 2508 'leanpub', 2509 'less', 2510 'line', 2511 'linkedin', 2512 'linkedin-in', 2513 'linode', 2514 'linux', 2515 'lyft', 2516 'magento', 2517 'maxcdn', 2518 'medapps', 2519 'medium', 2520 'medium-m', 2521 'medrt', 2522 'meetup', 2523 'microsoft', 2524 'mix', 2525 'mixcloud', 2526 'mizuni', 2527 'modx', 2528 'monero', 2529 'napster', 2530 'nintendo-switch', 2531 'node', 2532 'node-js', 2533 'npm', 2534 'ns8', 2535 'nutritionix', 2536 'odnoklassniki', 2537 'odnoklassniki-square', 2538 'opencart', 2539 'openid', 2540 'opera', 2541 'optin-monster', 2542 'osi', 2543 'page4', 2544 'pagelines', 2545 'palfed', 2546 'patreon', 2547 'paypal', 2548 'periscope', 2549 'phabricator', 2550 'phoenix-framework', 2551 'php', 2552 'pied-piper', 2553 'pied-piper-alt', 2554 'pied-piper-pp', 2555 'pinterest', 2556 'pinterest-p', 2557 'pinterest-square', 2558 'playstation', 2559 'product-hunt', 2560 'pushed', 2561 'python', 2562 'qq', 2563 'quinscape', 2564 'quora', 2565 'ravelry', 2566 'react', 2567 'rebel', 2568 'red-river', 2569 'reddit', 2570 'reddit-alien', 2571 'reddit-square', 2572 'rendact', 2573 'renren', 2574 'replyd', 2575 'resolving', 2576 'rocketchat', 2577 'rockrms', 2578 'safari', 2579 'sass', 2580 'schlix', 2581 'scribd', 2582 'searchengin', 2583 'sellcast', 2584 'sellsy', 2585 'servicestack', 2586 'shirtsinbulk', 2587 'simplybuilt', 2588 'sistrix', 2589 'skyatlas', 2590 'skype', 2591 'slack', 2592 'slack-hash', 2593 'slideshare', 2594 'snapchat', 2595 'snapchat-ghost', 2596 'snapchat-square', 2597 'soundcloud', 2598 'speakap', 2599 'spotify', 2600 'stack-exchange', 2601 'stack-overflow', 2602 'staylinked', 2603 'steam', 2604 'steam-square', 2605 'steam-symbol', 2606 'sticker-mule', 2607 'strava', 2608 'stripe', 2609 'stripe-s', 2610 'studiovinari', 2611 'stumbleupon', 2612 'stumbleupon-circle', 2613 'superpowers', 2614 'supple', 2615 'telegram', 2616 'telegram-plane', 2617 'tencent-weibo', 2618 'themeisle', 2619 'trello', 2620 'tripadvisor', 2621 'tumblr', 2622 'tumblr-square', 2623 'twitch', 2624 'twitter', 2625 'twitter-square', 2626 'typo3', 2627 'uber', 2628 'uikit', 2629 'uniregistry', 2630 'untappd', 2631 'usb', 2632 'ussunnah', 2633 'vaadin', 2634 'viacoin', 2635 'viadeo', 2636 'viadeo-square', 2637 'viber', 2638 'vimeo', 2639 'vimeo-square', 2640 'vimeo-v', 2641 'vine', 2642 'vk', 2643 'vnv', 2644 'vuejs', 2645 'weibo', 2646 'weixin', 2647 'whatsapp', 2648 'whatsapp-square', 2649 'whmcs', 2650 'wikipedia-w', 2651 'windows', 2652 'wordpress', 2653 'wordpress-simple', 2654 'wpbeginner', 2655 'wpexplorer', 2656 'wpforms', 2657 'xbox', 2658 'xing', 2659 'xing-square', 2660 'y-combinator', 2661 'yahoo', 2662 'yandex', 2663 'yandex-international', 2664 'yelp', 2665 'yoast', 2666 'youtube', 2667 'youtube-square', 2668 ]; 2669 2670 foreach ($classes as $index => $c) { 2671 if ($c === 'fa') { 2672 // FontAwesome 5 deprecated the use of fa prefix in favour of fas, far and fab 2673 unset($classes[$index]); 2674 continue; 2675 } 2676 2677 if (preg_match_all('/^elgg-icon-(.+)/i', $c)) { 2678 // convert 2679 $base_icon = preg_replace('/^elgg-icon-(.+)/i', '$1', $c); 2680 2681 if ($map_sprites) { 2682 if (strpos($base_icon, '-hover') !== false) { 2683 $base_icon = str_replace('-hover', '', $base_icon); 2684 $classes[] = 'elgg-state'; 2685 $classes[] = 'elgg-state-notice'; 2686 } 2687 2688 $base_icon = elgg_extract($base_icon, $legacy_sprites, $base_icon); 2689 } 2690 2691 // map solid/regular/light iconnames to correct classes 2692 if (preg_match('/.*-solid$/', $base_icon)) { 2693 $base_icon = preg_replace('/(.*)-solid$/', '$1', $base_icon); 2694 $classes[] = 'fas'; 2695 } elseif (preg_match('/.*-regular$/', $base_icon)) { 2696 $base_icon = preg_replace('/(.*)-regular$/', '$1', $base_icon); 2697 $classes[] = 'far'; 2698 } elseif (preg_match('/.*-light$/', $base_icon)) { 2699 // currently light is only available in FontAwesome 5 Pro 2700 $base_icon = preg_replace('/(.*)-light$/', '$1', $base_icon); 2701 $classes[] = 'fal'; 2702 } else { 2703 if (array_key_exists($base_icon, $fa5)) { 2704 $classes[] = $fa5[$base_icon][1]; 2705 $base_icon = $fa5[$base_icon][0]; 2706 } else if (in_array($base_icon, $brands)) { 2707 $classes[] = 'fab'; 2708 } else { 2709 $classes[] = 'fas'; 2710 } 2711 } 2712 2713 $classes[] = "fa-{$base_icon}"; 2714 } 2715 } 2716 2717 $classes = array_unique($classes); 2718 2719 return elgg_trigger_plugin_hook('classes', 'icon', null, $classes); 2720} 2721