<?php
// Exit if accessed directly
if( ! defined( 'ABSPATH' ) ) exit;

function wpforo_get_modules_info( $base = null ): array {
	$modules = [
		'tags'          => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.5.2',
			'class'    => 'wpforo\classes\Topics',
			'deps'     => [],
			'title'    => 'Topic Tags',
			'thumb'    => '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="122.879px" height="50px" viewBox="0 0 122.879 106.533" enable-background="new 0 0 122.879 106.533" xml:space="preserve"><g><path d="M79.704,105.239c-1.2,1.072-3.042,0.971-4.115-0.229c-1.073-1.2-0.971-3.042,0.229-4.115l39.432-35.204l0.012-0.01v-0.001 c0.569-0.503,0.995-1.045,1.281-1.629c0.281-0.574,0.443-1.229,0.487-1.963c0.044-0.732-0.041-1.404-0.255-2.016 c-0.213-0.605-0.572-1.192-1.077-1.761l-0.048-0.055L70.895,9.569c-1.092-1.187-1.015-3.035,0.172-4.126s3.034-1.015,4.126,0.172 l44.758,48.693c0.038,0.039,0.075,0.078,0.112,0.119c1.004,1.127,1.744,2.37,2.22,3.727c0.474,1.352,0.665,2.777,0.575,4.277 c-0.09,1.497-0.448,2.89-1.081,4.182c-0.629,1.283-1.512,2.427-2.653,3.435l-0.001-0.001L79.704,105.239L79.704,105.239z M54.383,3.131h0.738v0.006c0.788,0,1.574,0.317,2.149,0.942l44.758,48.694c0.038,0.039,0.075,0.078,0.111,0.119 c1.005,1.128,1.745,2.37,2.221,3.727c0.474,1.352,0.665,2.777,0.575,4.278c-0.091,1.496-0.449,2.89-1.082,4.181 c-0.628,1.283-1.511,2.427-2.653,3.435v-0.001l-39.291,35.077c-0.026,0.028-0.055,0.055-0.083,0.082 c-1.113,1.032-2.346,1.787-3.698,2.266c-1.34,0.474-2.774,0.666-4.302,0.575c-1.53-0.091-2.936-0.451-4.219-1.076 c-1.287-0.628-2.435-1.511-3.447-2.647l-42.083-47.2l0.001-0.001c-0.505-0.567-0.79-1.33-0.732-2.147l0.086-1.181L0.006,3.114h0 c-0.008-0.124-0.009-0.25,0-0.377c0.101-1.609,1.487-2.833,3.097-2.731L53.451,3.22c0.063-0.01,0.127-0.02,0.192-0.028 C53.934,3.151,54.182,3.131,54.383,3.131L54.383,3.131z M53.922,9.068c-0.195,0.019-0.389,0.018-0.579-0.002L6.066,6.047 l3.208,46.027c0.009,0.135,0.009,0.269-0.001,0.4h0.001l-0.011,0.148l41.261,46.28c0.498,0.559,1.045,0.984,1.642,1.275 c0.6,0.292,1.267,0.462,2.001,0.505c0.738,0.044,1.41-0.042,2.016-0.256c0.594-0.21,1.149-0.554,1.663-1.031 c0.059-0.055,0.12-0.106,0.182-0.155l39.296-35.082l0.012-0.011l-0.001-0.001c0.569-0.502,0.996-1.045,1.281-1.628 c0.282-0.575,0.443-1.229,0.488-1.964c0.044-0.732-0.042-1.404-0.256-2.016c-0.213-0.605-0.571-1.192-1.077-1.761l-0.048-0.055 L53.922,9.068L53.922,9.068z M24.268,12.234c1.474,0.04,2.833,0.335,4.078,0.885c1.256,0.555,2.392,1.36,3.406,2.413l0.013,0.015 c1.006,1.042,1.766,2.204,2.278,3.485c0.519,1.298,0.779,2.68,0.779,4.145c0,0.127-0.008,0.252-0.024,0.375 c-0.071,1.347-0.37,2.605-0.895,3.772l-0.005,0.01l0.005,0.002c-0.563,1.251-1.38,2.386-2.446,3.404 c-0.063,0.06-0.128,0.117-0.195,0.17c-1.023,0.923-2.145,1.621-3.363,2.09c-1.271,0.489-2.614,0.72-4.031,0.69v0.01 c-1.463,0-2.838-0.278-4.123-0.832c-1.265-0.544-2.419-1.354-3.459-2.424l-0.012-0.012v0c-1.028-1.067-1.796-2.246-2.304-3.537 c-0.504-1.285-0.738-2.663-0.698-4.133c0.039-1.495,0.334-2.873,0.892-4.135c0.564-1.278,1.387-2.43,2.473-3.457 c1.058-1,2.232-1.75,3.521-2.247S22.818,12.195,24.268,12.234L24.268,12.234z M25.991,18.468c-0.57-0.251-1.19-0.387-1.86-0.405 c-0.681-0.019-1.305,0.082-1.87,0.3c-0.568,0.22-1.11,0.573-1.625,1.059c-0.5,0.472-0.874,0.992-1.124,1.56 c-0.257,0.582-0.394,1.222-0.412,1.918c-0.019,0.674,0.085,1.297,0.31,1.869c0.224,0.571,0.582,1.11,1.069,1.617l0,0 c0.485,0.497,1.01,0.867,1.574,1.109c0.543,0.234,1.148,0.351,1.814,0.351v0.011l0.069,0.001c0.688,0.019,1.315-0.082,1.882-0.3 c0.528-0.203,1.032-0.518,1.512-0.943c0.033-0.036,0.068-0.07,0.104-0.104c0.506-0.483,0.888-1.009,1.143-1.576l0.004,0.002 c0.224-0.502,0.355-1.047,0.392-1.636c-0.002-0.041-0.003-0.083-0.003-0.125c0-0.74-0.115-1.398-0.345-1.973 c-0.231-0.579-0.586-1.115-1.063-1.607l-0.018-0.018C27.072,19.087,26.554,18.717,25.991,18.468L25.991,18.468z M52.422,31.36 c-1.092-1.187-1.015-3.035,0.171-4.126c1.187-1.092,3.034-1.015,4.126,0.172l24.931,27.04c1.092,1.187,1.015,3.034-0.172,4.126 s-3.034,1.016-4.126-0.172L52.422,31.36L52.422,31.36z M40.49,42.661c-1.092-1.187-1.015-3.034,0.171-4.126 c1.187-1.092,3.034-1.016,4.126,0.171l24.931,27.04c1.092,1.187,1.015,3.034-0.172,4.126c-1.188,1.092-3.035,1.016-4.127-0.171 L40.49,42.661L40.49,42.661z M28.074,53.962c-1.092-1.187-1.015-3.035,0.171-4.126c1.187-1.092,3.035-1.015,4.126,0.171 l24.931,27.04c1.092,1.188,1.016,3.034-0.171,4.126s-3.034,1.016-4.126-0.171L28.074,53.962L28.074,53.962z"></path></g></svg>',
			'desc'     => __( 'Adds Tags Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'subscriptions' => [
			'base'     => true,
			'version'  => '1.0.0',
			'requires' => '1.0.2',
			'class'    => 'wpforo\modules\subscriptions\Subscriptions',
			'deps'     => [],
			'title'    => 'Subscriptions',
			'thumb'    => '<svg height="54px" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 117.88 122.88"><path d="M102.31,30.86,78,5.74V5.49H12.44V46h0L46.66,76.6l.15.14L58.36,87.06,105,46.36v-10l-2.64-5.49ZM64.07,28.44l.09-.41.56-2.47h6.83L67.62,44.1c-.07.33-.14.63-.19.9h0a9.34,9.34,0,0,0-.18,1.42,1.43,1.43,0,0,0,.29.94,1,1,0,0,0,.72.33c.93,0,2.14-.61,3.61-1.82a16.05,16.05,0,0,0,3.85-5,15.19,15.19,0,0,0,.47-12.71,13.86,13.86,0,0,0-3.4-4.76,16,16,0,0,0-5.5-3.15,22.48,22.48,0,0,0-7.41-1.14,25.87,25.87,0,0,0-7.73,1.11A19.49,19.49,0,0,0,46,23.41a17.46,17.46,0,0,0-4.45,5.08,22.14,22.14,0,0,0-2.4,5.84,25.72,25.72,0,0,0-.83,6.54A18.36,18.36,0,0,0,40.07,49a16.3,16.3,0,0,0,5.14,6.18,21,21,0,0,0,8.16,3.55,34.53,34.53,0,0,0,10.8.42A25.52,25.52,0,0,0,72.27,57a15.69,15.69,0,0,0,5.63-4.5h5.51a18.14,18.14,0,0,1-3.5,5,20.31,20.31,0,0,1-5.06,3.66,26.25,26.25,0,0,1-6.49,2.26,36.89,36.89,0,0,1-7.84.77,36.4,36.4,0,0,1-11.44-1.67,23.41,23.41,0,0,1-8.58-4.81,20.32,20.32,0,0,1-5.27-7.5,25.24,25.24,0,0,1-1.77-9.62A28.31,28.31,0,0,1,35.3,30.22,23.54,23.54,0,0,1,49,16.39a29.27,29.27,0,0,1,11-2,27,27,0,0,1,9,1.44A19.93,19.93,0,0,1,76,20a18.12,18.12,0,0,1,4.47,6.38A21,21,0,0,1,82,34.46a19.13,19.13,0,0,1-1.24,6.78,19.37,19.37,0,0,1-3.56,6,17.25,17.25,0,0,1-5.44,4.19A15.06,15.06,0,0,1,65,53a6.17,6.17,0,0,1-3.25-.72A3.48,3.48,0,0,1,60.22,50,12,12,0,0,1,59,51a10.61,10.61,0,0,1-1.06.68,9.49,9.49,0,0,1-2.89,1.07,9.65,9.65,0,0,1-5.67-.55A9.38,9.38,0,0,1,45,48.44a12,12,0,0,1-1.69-6.56,19.55,19.55,0,0,1,1.74-8,16.59,16.59,0,0,1,5-6.44,11.56,11.56,0,0,1,7.29-2.51A7.46,7.46,0,0,1,64,28.25l.12.19ZM49.93,41.83a7.62,7.62,0,0,0,1.29,4.76,4.1,4.1,0,0,0,3.38,1.66,5.75,5.75,0,0,0,4.28-2,10,10,0,0,0,1-1.23,10.61,10.61,0,0,0,.6-.93,13.67,13.67,0,0,0,1.2-2.74,18.71,18.71,0,0,0,1-5.66v-.07a10.84,10.84,0,0,0-.06-1.16,7.48,7.48,0,0,0-.26-1.3,5.92,5.92,0,0,0-1-1.93A4.18,4.18,0,0,0,59.84,30a4.76,4.76,0,0,0-2-.42,5.74,5.74,0,0,0-4.1,1.78,11.34,11.34,0,0,0-2.78,4.54h0l0,0a17.92,17.92,0,0,0-1,5.86Zm60.51-.26a3.59,3.59,0,0,1,2.43-.93,4,4,0,0,1,2.06.6,5.09,5.09,0,0,1,1.26,1.07,7.06,7.06,0,0,1,1.69,4.26v70.64a5.71,5.71,0,0,1-1.66,4h0a5.67,5.67,0,0,1-4,1.66H5.67a5.71,5.71,0,0,1-4-1.66h0a5.62,5.62,0,0,1-1.66-4V46.57a7.1,7.1,0,0,1,1.73-4.32,5.5,5.5,0,0,1,1.26-1,4,4,0,0,1,2-.58,3.59,3.59,0,0,1,2,.57V2.74A2.74,2.74,0,0,1,9.7,0H78.9A2.71,2.71,0,0,1,81,1l28.65,29.59a2.71,2.71,0,0,1,.78,1.9h0v.79c0,.11,0,.22,0,.34s0,.22,0,.33v7.63ZM56.52,92.74,44.9,82.36,5.49,116.9v.31a.17.17,0,0,0,0,.13h0a.22.22,0,0,0,.13,0H112.21a.22.22,0,0,0,.13,0h0a.17.17,0,0,0,0-.13V117L72.45,82,60.15,92.76h0a2.73,2.73,0,0,1-3.62,0Zm-15.73-14L5.49,47.15v62.48l35.3-30.94Zm71.6,31.06V47.12L76.6,78.39l35.79,31.36Z"></path></svg>',
			'desc'     => __( 'Adds Subscriptions & Mentioning Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'mentioning'    => [
			'base'     => true,
			'version'  => '1.0.0',
			'requires' => '2.0.0',
			'class'    => 'wpforo\modules\mentioning\Mentioning',
			'deps'     => [ 'wpforo\modules\subscriptions\Subscriptions' ],
			'title'    => 'User Mentioning System',
			'thumb'    => '<svg height="54px" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 117.88 122.88"><path d="M102.31,30.86,78,5.74V5.49H12.44V46h0L46.66,76.6l.15.14L58.36,87.06,105,46.36v-10l-2.64-5.49ZM64.07,28.44l.09-.41.56-2.47h6.83L67.62,44.1c-.07.33-.14.63-.19.9h0a9.34,9.34,0,0,0-.18,1.42,1.43,1.43,0,0,0,.29.94,1,1,0,0,0,.72.33c.93,0,2.14-.61,3.61-1.82a16.05,16.05,0,0,0,3.85-5,15.19,15.19,0,0,0,.47-12.71,13.86,13.86,0,0,0-3.4-4.76,16,16,0,0,0-5.5-3.15,22.48,22.48,0,0,0-7.41-1.14,25.87,25.87,0,0,0-7.73,1.11A19.49,19.49,0,0,0,46,23.41a17.46,17.46,0,0,0-4.45,5.08,22.14,22.14,0,0,0-2.4,5.84,25.72,25.72,0,0,0-.83,6.54A18.36,18.36,0,0,0,40.07,49a16.3,16.3,0,0,0,5.14,6.18,21,21,0,0,0,8.16,3.55,34.53,34.53,0,0,0,10.8.42A25.52,25.52,0,0,0,72.27,57a15.69,15.69,0,0,0,5.63-4.5h5.51a18.14,18.14,0,0,1-3.5,5,20.31,20.31,0,0,1-5.06,3.66,26.25,26.25,0,0,1-6.49,2.26,36.89,36.89,0,0,1-7.84.77,36.4,36.4,0,0,1-11.44-1.67,23.41,23.41,0,0,1-8.58-4.81,20.32,20.32,0,0,1-5.27-7.5,25.24,25.24,0,0,1-1.77-9.62A28.31,28.31,0,0,1,35.3,30.22,23.54,23.54,0,0,1,49,16.39a29.27,29.27,0,0,1,11-2,27,27,0,0,1,9,1.44A19.93,19.93,0,0,1,76,20a18.12,18.12,0,0,1,4.47,6.38A21,21,0,0,1,82,34.46a19.13,19.13,0,0,1-1.24,6.78,19.37,19.37,0,0,1-3.56,6,17.25,17.25,0,0,1-5.44,4.19A15.06,15.06,0,0,1,65,53a6.17,6.17,0,0,1-3.25-.72A3.48,3.48,0,0,1,60.22,50,12,12,0,0,1,59,51a10.61,10.61,0,0,1-1.06.68,9.49,9.49,0,0,1-2.89,1.07,9.65,9.65,0,0,1-5.67-.55A9.38,9.38,0,0,1,45,48.44a12,12,0,0,1-1.69-6.56,19.55,19.55,0,0,1,1.74-8,16.59,16.59,0,0,1,5-6.44,11.56,11.56,0,0,1,7.29-2.51A7.46,7.46,0,0,1,64,28.25l.12.19ZM49.93,41.83a7.62,7.62,0,0,0,1.29,4.76,4.1,4.1,0,0,0,3.38,1.66,5.75,5.75,0,0,0,4.28-2,10,10,0,0,0,1-1.23,10.61,10.61,0,0,0,.6-.93,13.67,13.67,0,0,0,1.2-2.74,18.71,18.71,0,0,0,1-5.66v-.07a10.84,10.84,0,0,0-.06-1.16,7.48,7.48,0,0,0-.26-1.3,5.92,5.92,0,0,0-1-1.93A4.18,4.18,0,0,0,59.84,30a4.76,4.76,0,0,0-2-.42,5.74,5.74,0,0,0-4.1,1.78,11.34,11.34,0,0,0-2.78,4.54h0l0,0a17.92,17.92,0,0,0-1,5.86Zm60.51-.26a3.59,3.59,0,0,1,2.43-.93,4,4,0,0,1,2.06.6,5.09,5.09,0,0,1,1.26,1.07,7.06,7.06,0,0,1,1.69,4.26v70.64a5.71,5.71,0,0,1-1.66,4h0a5.67,5.67,0,0,1-4,1.66H5.67a5.71,5.71,0,0,1-4-1.66h0a5.62,5.62,0,0,1-1.66-4V46.57a7.1,7.1,0,0,1,1.73-4.32,5.5,5.5,0,0,1,1.26-1,4,4,0,0,1,2-.58,3.59,3.59,0,0,1,2,.57V2.74A2.74,2.74,0,0,1,9.7,0H78.9A2.71,2.71,0,0,1,81,1l28.65,29.59a2.71,2.71,0,0,1,.78,1.9h0v.79c0,.11,0,.22,0,.34s0,.22,0,.33v7.63ZM56.52,92.74,44.9,82.36,5.49,116.9v.31a.17.17,0,0,0,0,.13h0a.22.22,0,0,0,.13,0H112.21a.22.22,0,0,0,.13,0h0a.17.17,0,0,0,0-.13V117L72.45,82,60.15,92.76h0a2.73,2.73,0,0,1-3.62,0Zm-15.73-14L5.49,47.15v62.48l35.3-30.94Zm71.6,31.06V47.12L76.6,78.39l35.79,31.36Z"></path></svg>',
			'desc'     => __( 'Adds User Following System Feature For Users', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'follows'       => [
			'base'     => true,
			'version'  => '1.0.0',
			'requires' => '2.0.0',
			'class'    => 'wpforo\modules\follows\Follows',
			'deps'     => [ 'wpforo\modules\subscriptions\Subscriptions' ],
			'title'    => 'User Following System',
			'thumb'    => '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 116.42 122.88" style="enable-background:new 0 0 116.42 122.88" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#3AAF3C;}</style><g><path class="st0" d="M5.95,112.26c-5.1-0.39-6.33-4.06-5.86-8.29c2.79-24.96,30.78-17.73,42.03-27.86l0,0 c5.61,16.5,29.05,17.11,34.31,0c1.21,1.09,2.89,2.01,4.87,2.82c-1.81,1.1-3.52,2.43-5.09,4c-7.93,7.92-9.88,19.57-5.86,29.33H5.95 L5.95,112.26z M41.97,59.56c2.13,3.37,4.36,6.83,7.12,9.37c2.66,2.43,5.9,4.09,10.16,4.1c4.64,0.01,8.01-1.7,10.76-4.28 c2.86-2.67,5.11-6.34,7.34-10l5.98-9.84c1.11-2.55,1.52-4.25,1.26-5.25c-0.16-0.59-0.81-0.88-1.92-0.93 c-0.23-0.01-0.48-0.01-0.72-0.01c-0.26,0.01-0.54,0.03-0.84,0.05c-0.17,0.01-0.31,0-0.46-0.03c-0.52,0.03-1.08-0.01-1.63-0.09 l2.04-9.06c-15.19,2.39-26.55-8.88-42.59-2.25l1.16,10.67c-0.63,0.04-1.25,0.01-1.82-0.07C28.6,42.24,40.16,56.67,41.97,59.56 L41.97,59.56L41.97,59.56L41.97,59.56z M84.74,40.01c1.47,0.45,2.41,1.38,2.8,2.89c0.43,1.67-0.04,4.03-1.46,7.25l0,0 c-0.03,0.06-0.05,0.12-0.09,0.17l-6.04,9.95c-2.33,3.84-4.69,7.69-7.85,10.63c-3.26,3.06-7.3,5.1-12.81,5.08 c-5.14-0.01-9.02-1.97-12.2-4.89c-3.84-3.52-21.52-25.66-13.62-30.99c0.39-0.25,0.82-0.48,1.28-0.65 c-0.35-4.58-0.47-10.34-0.25-15.17c0.12-1.14,0.34-2.28,0.65-3.43c1.35-4.85,4.76-8.75,8.96-11.43c2.32-1.48,4.87-2.59,7.51-3.33 c1.68-0.48-1.43-5.87,0.3-6.03c8.41-0.87,22.05,6.82,27.93,13.19c2.93,3.18,4.8,7.41,5.2,13L84.74,40.01L84.74,40.01L84.74,40.01 L84.74,40.01L84.74,40.01L84.74,40.01z"/><path class="st1" d="M95.32,80.66c11.66,0,21.11,9.45,21.11,21.11c0,11.66-9.45,21.11-21.11,21.11c-11.66,0-21.11-9.45-21.11-21.11 C74.21,90.11,83.66,80.66,95.32,80.66L95.32,80.66L95.32,80.66L95.32,80.66z M87.77,100.17c1.58,0.91,2.61,1.67,3.83,3.02 c3.17-5.11,6.62-7.94,11.1-11.97l0.44-0.17h4.91c-6.58,7.3-11.68,13.33-16.24,22.13c-2.38-5.08-4.5-8.59-9.23-11.84L87.77,100.17 L87.77,100.17L87.77,100.17z"/></g></svg>',
			'desc'     => __( 'Adds User Following System Feature For Users', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'notifications' => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.7.0',
			'class'    => 'wpforo\classes\Activity',
			'deps'     => [],
			'title'    => 'Forum Notifications',
			'thumb'    => '<svg version="1.1" height="50px" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 116.11 122.88" style="enable-background:new 0 0 116.11 122.88" xml:space="preserve"><g><path d="M74.82,109.04c-0.37,1.94-1.02,3.72-1.96,5.35c-0.97,1.67-2.24,3.18-3.8,4.5c-1.57,1.32-3.27,2.32-5.12,2.99 c-1.85,0.67-3.81,1-5.88,1c-2.08,0-4.04-0.34-5.88-1c-1.84-0.67-3.55-1.66-5.11-2.99c-1.57-1.33-2.83-2.83-3.8-4.5 c-0.97-1.67-1.63-3.51-1.99-5.53c-0.18-0.98,0.48-1.92,1.46-2.1c0.03,0,0.32-0.03,0.32-0.03h30.02c1,0,1.82,0.81,1.82,1.82 C74.89,108.72,74.86,108.88,74.82,109.04L74.82,109.04L74.82,109.04z M20.21,0.25c1.83-0.73,3.9,0.17,4.63,2 c0.73,1.83-0.17,3.9-2,4.63c-3.96,1.58-7.28,3.77-9.93,6.61c-2.64,2.84-4.63,6.36-5.93,10.59c-0.58,1.88-2.58,2.94-4.46,2.36 c-1.88-0.58-2.94-2.58-2.36-4.46c1.63-5.3,4.15-9.74,7.52-13.36C11.05,5.01,15.24,2.23,20.21,0.25L20.21,0.25z M93.27,6.88 c-1.83-0.73-2.73-2.8-2-4.63c0.73-1.83,2.8-2.73,4.63-2c4.97,1.98,9.16,4.76,12.53,8.38c3.37,3.63,5.9,8.07,7.52,13.36 c0.58,1.88-0.48,3.88-2.36,4.46c-1.88,0.58-3.88-0.48-4.46-2.36c-1.3-4.24-3.29-7.76-5.93-10.59 C100.55,10.65,97.23,8.46,93.27,6.88L93.27,6.88z M67.62,10.54c1.47,0.38,2.9,0.85,4.29,1.4c2.04,0.81,4,1.78,5.88,2.91 c0.07,0.05,0.15,0.09,0.22,0.14c1.8,1.11,3.48,2.33,5.02,3.65c1.62,1.39,3.12,2.92,4.52,4.6l0.01,0.01h0 c1.37,1.65,2.59,3.42,3.67,5.29c1.08,1.88,2.01,3.84,2.78,5.86l0,0c0.79,2.09,1.38,4.22,1.78,6.41c0.39,2.2,0.59,4.45,0.59,6.76 c0,4.56,0,7.03,0,7.33c0.01,2.34,0.02,4.63,0.04,6.86v0.02l0,0c0.01,2.02,0.14,4.05,0.39,6.08c0.25,2.01,0.61,3.95,1.08,5.82l0,0 c0.47,1.84,1.11,3.62,1.9,5.32c0.82,1.75,1.82,3.47,2.99,5.14l0.01,0c1.16,1.64,2.61,3.27,4.35,4.87c1.8,1.65,3.88,3.28,6.26,4.86 c1.36,0.91,1.73,2.76,0.81,4.12c-0.57,0.85-1.51,1.32-2.47,1.32v0.01l-26.85,0H58.06H31.21H4.37c-1.65,0-2.98-1.33-2.98-2.98 c0-1.08,0.58-2.03,1.44-2.55c2.41-1.63,4.48-3.25,6.21-4.85c1.72-1.59,3.16-3.22,4.32-4.9c0.03-0.05,0.07-0.1,0.11-0.14 c1.12-1.64,2.08-3.31,2.87-5.01c0.81-1.73,1.46-3.51,1.94-5.34c0.01-0.04,0.02-0.08,0.03-0.11c0.46-1.78,0.81-3.66,1.05-5.63 c0.24-1.98,0.37-4.03,0.37-6.14v-14.1c0-2.27,0.2-4.52,0.61-6.77c0.41-2.24,1-4.39,1.79-6.44c0.78-2.05,1.72-4.02,2.81-5.9 c1.08-1.87,2.32-3.64,3.71-5.32l0.02-0.02l0,0c1.38-1.65,2.9-3.19,4.55-4.6c1.63-1.39,3.39-2.66,5.28-3.79 c1.91-1.14,3.89-2.1,5.93-2.88c1.42-0.54,2.89-0.99,4.39-1.36c0.51-1.79,1.39-3.24,2.64-4.35c1.72-1.53,3.98-2.29,6.79-2.26 c2.78,0.02,5.03,0.79,6.73,2.32C66.22,7.32,67.11,8.76,67.62,10.54L67.62,10.54L67.62,10.54z M69.75,17.47 c-1.65-0.65-3.33-1.16-5.04-1.53c-1.32-0.17-2.4-1.21-2.57-2.59c-0.16-1.3-0.53-2.21-1.12-2.73c-0.59-0.52-1.53-0.79-2.82-0.8 c-1.29-0.01-2.22,0.24-2.79,0.75c-0.58,0.52-0.95,1.44-1.1,2.76h0c-0.14,1.26-1.09,2.34-2.41,2.58c-1.85,0.35-3.64,0.85-5.37,1.51 c-1.73,0.65-3.38,1.46-4.98,2.41c-1.59,0.95-3.08,2.02-4.46,3.21c-1.38,1.18-2.67,2.48-3.85,3.9l0,0c-1.16,1.4-2.2,2.91-3.13,4.51 c-0.91,1.58-1.71,3.26-2.39,5.04c-0.68,1.77-1.18,3.57-1.51,5.37c-0.33,1.81-0.49,3.72-0.49,5.72v14.1c0,2.34-0.14,4.62-0.41,6.86 c-0.27,2.15-0.67,4.29-1.22,6.4c-0.01,0.05-0.02,0.09-0.03,0.14c-0.57,2.15-1.34,4.26-2.31,6.34c-0.94,2.01-2.06,3.96-3.35,5.85 c-0.04,0.06-0.08,0.12-0.12,0.18c-1.36,1.96-3.09,3.91-5.18,5.85l-0.08,0.07h18.22h26.85H84.9h18.19c-2.04-1.88-3.76-3.82-5.16-5.8 l0,0l0-0.01c-1.37-1.96-2.54-3.97-3.51-6.03c-0.99-2.1-1.75-4.23-2.3-6.37l0,0l0-0.01c-0.54-2.13-0.95-4.32-1.22-6.56 c-0.26-2.14-0.4-4.4-0.41-6.77v-0.01c-0.02-2.21-0.03-4.51-0.04-6.91c-0.02-4.44-0.02-6.86-0.02-7.33c0-1.96-0.16-3.87-0.5-5.72 c-0.33-1.84-0.82-3.62-1.47-5.34l0,0l0-0.01c-0.67-1.77-1.46-3.44-2.36-5.01c-0.9-1.57-1.94-3.06-3.11-4.48l0,0 c-1.16-1.39-2.43-2.68-3.81-3.87c-1.34-1.15-2.76-2.18-4.25-3.11c-0.07-0.03-0.13-0.07-0.2-0.11 C73.11,18.97,71.45,18.15,69.75,17.47L69.75,17.47L69.75,17.47z"></path></g></svg>',
			'desc'     => __( 'Adds Forum Notifications Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'logging'       => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.0.0',
			'class'    => 'wpforo\classes\Logs',
			'deps'     => [],
			'title'    => 'Action Logging & Views',
			'thumb'    => '<svg height="55px" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 109.21 122.88"><title>mouse-click</title><path d="M86,122.31a5.57,5.57,0,0,1-.9.35,5.09,5.09,0,0,1-1,.18,5.46,5.46,0,0,1-1,0,6.77,6.77,0,0,1-1-.15,6,6,0,0,1-1-.36l0,0a5.51,5.51,0,0,1-.92-.53l0,0a6.41,6.41,0,0,1-.78-.69,5.19,5.19,0,0,1-.65-.87l-9.08-14.88-7.69,9a15.49,15.49,0,0,1-1.1,1.18c-.39.37-.78.71-1.18,1l-.08.06a12.19,12.19,0,0,1-1.2.82,9.66,9.66,0,0,1-1.24.63,6.91,6.91,0,0,1-1,.37,6.21,6.21,0,0,1-1,.22,7.55,7.55,0,0,1-1.06.07,7.19,7.19,0,0,1-1-.11,6.14,6.14,0,0,1-1.18-.35,5.42,5.42,0,0,1-1.06-.57,6.22,6.22,0,0,1-.92-.78l0,0a7.31,7.31,0,0,1-.75-1l-.11-.2-.09-.21L47.72,112l0-.17L40.91,43.26a4.52,4.52,0,0,1,0-1.33,4.3,4.3,0,0,1,.43-1.25,4.31,4.31,0,0,1,1.39-1.55l0,0a3.82,3.82,0,0,1,.9-.46,4.25,4.25,0,0,1,1-.24h0a4.31,4.31,0,0,1,1.29.05,4.67,4.67,0,0,1,1.25.44l.3.16c13.51,8.84,26.1,17.06,38.64,25.25l19,12.39a11.72,11.72,0,0,1,1,.72l0,0a8.78,8.78,0,0,1,.82.73l.06.07a7.41,7.41,0,0,1,.71.82,5.91,5.91,0,0,1,.57.87,6.42,6.42,0,0,1,.51,1.14,5.6,5.6,0,0,1,.26,1.17,5.44,5.44,0,0,1,0,1.21h0a6.59,6.59,0,0,1-.23,1.19,6.54,6.54,0,0,1-.94,1.88,6.41,6.41,0,0,1-.67.83,7.45,7.45,0,0,1-.82.76,10.42,10.42,0,0,1-1.16.83,12.92,12.92,0,0,1-1.34.7c-.47.21-1,.41-1.46.58a14.27,14.27,0,0,1-1.55.43h0c-2.77.54-5.53,1.21-8.27,1.87l-3.25.77,9,14.94a5.84,5.84,0,0,1,.46,1,5.59,5.59,0,0,1,.15,3.21l0,.1a5.53,5.53,0,0,1-.33.94,6.43,6.43,0,0,1-.51.89,5.62,5.62,0,0,1-.68.81,6,6,0,0,1-.82.67l-2,1.29A83,83,0,0,1,86,122.31ZM37.63,19.46a4,4,0,0,1-6.92,4l-8-14a4,4,0,0,1,6.91-4l8.06,14Zm-15,46.77a4,4,0,0,1,4,6.91l-14,8.06a4,4,0,0,1-4-6.91l14-8.06ZM20.56,39.84a4,4,0,0,1-2.07,7.72L3,43.36A4,4,0,0,1,5,35.64l15.53,4.2ZM82,41.17a4,4,0,0,1-4-6.91L92,26.2a4,4,0,0,1,4,6.91L82,41.17ZM63.46,20.57a4,4,0,1,1-7.71-2.06L59.87,3A4,4,0,0,1,67.59,5L63.46,20.57Zm20.17,96.36,9.67-5.86c-3.38-5.62-8.85-13.55-11.51-19.17a2.17,2.17,0,0,1-.12-.36,2.4,2.4,0,0,1,1.81-2.87c5.38-1.23,10.88-2.39,16.22-3.73a10.28,10.28,0,0,0,1.8-.58,6.11,6.11,0,0,0,1.3-.77,3.38,3.38,0,0,0,.38-.38.9.9,0,0,0,.14-.24l-.06-.18a2.15,2.15,0,0,0-.44-.53,5.75,5.75,0,0,0-.83-.63L47.06,45.75c2.11,21.36,5.2,44.1,6.45,65.31a6.28,6.28,0,0,0,.18,1,2.89,2.89,0,0,0,.26.62l.13.14a1,1,0,0,0,.29,0,2.76,2.76,0,0,0,.51-.17,5.71,5.71,0,0,0,1.28-.79,11.22,11.22,0,0,0,1.35-1.33c1.93-2.27,9.6-12.14,11.4-13.18a2.4,2.4,0,0,1,3.28.82l11.44,18.75Z"></path></svg>',
			'desc'     => __( 'Adds Action Logging & Views Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'seo'           => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.0.0',
			'class'    => 'wpforo\classes\SEO',
			'deps'     => [],
			'title'    => 'wpForo SEO',
			'thumb'    => '<svg height="55px" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 111.56 122.88"><defs><style>.cls-5514{fill-rule:evenodd;}</style></defs><title>testing</title><path class="cls-5514" d="M79.86,65.67a25,25,0,0,1,20.89,38.62l10.81,11.78-7.46,6.81L93.68,111.42A25,25,0,1,1,79.86,65.67Zm-42.65.26a2.74,2.74,0,0,1-2.6-2.84,2.71,2.71,0,0,1,2.6-2.84h15.4a2.76,2.76,0,0,1,2.6,2.84,2.71,2.71,0,0,1-2.6,2.84ZM22.44,57.22a5.67,5.67,0,1,1-5.67,5.67,5.67,5.67,0,0,1,5.67-5.67Zm2-18.58a2,2,0,0,1,2.85,0,2.07,2.07,0,0,1,0,2.89l-2,2,2,2a2,2,0,0,1,0,2.87,2,2,0,0,1-2.84,0l-2-2-2,2a2,2,0,0,1-2.86,0,2.07,2.07,0,0,1,0-2.89l2-2-2-2.05a2,2,0,0,1,2.87-2.86l2,2,2-2ZM16.85,21.52a2.29,2.29,0,0,1,3.16.63l1.13,1.36,4-5.05a2.27,2.27,0,1,1,3.51,2.88l-5.86,7.34a2.48,2.48,0,0,1-.55.52,2.28,2.28,0,0,1-3.16-.63l-2.84-3.89a2.28,2.28,0,0,1,.63-3.16Zm66.51-4.25h9.32a6.69,6.69,0,0,1,6.66,6.65v30.9c-.2,2.09-5.31,2.11-5.75,0V23.92a.93.93,0,0,0-.27-.67.91.91,0,0,0-.67-.27H83.32V54.82c-.49,1.89-4.75,2.18-5.71,0V6.66A1,1,0,0,0,77.34,6a.93.93,0,0,0-.67-.27h-70A.93.93,0,0,0,6,6a1,1,0,0,0-.27.68V85.79a1,1,0,0,0,.27.68.93.93,0,0,0,.67.27H44.74c2.88.29,3,5.27,0,5.71H21.66v10.61a.92.92,0,0,0,.94.94H44.74c2.09.24,2.76,5,0,5.71H22.64a6.54,6.54,0,0,1-4.7-2,6.63,6.63,0,0,1-2-4.7V92.45H6.66A6.69,6.69,0,0,1,0,85.79V6.66A6.54,6.54,0,0,1,2,2a6.61,6.61,0,0,1,4.7-2h70a6.55,6.55,0,0,1,4.7,2,6.65,6.65,0,0,1,2,4.7V17.27ZM37.18,26.44a2.75,2.75,0,0,1-2.6-2.84,2.71,2.71,0,0,1,2.6-2.84H63.86a2.74,2.74,0,0,1,2.6,2.84,2.71,2.71,0,0,1-2.6,2.84Zm0,19.74a2.74,2.74,0,0,1-2.6-2.83,2.71,2.71,0,0,1,2.6-2.84H63.86a2.74,2.74,0,0,1,2.6,2.84,2.7,2.7,0,0,1-2.6,2.83ZM70.45,93a3.46,3.46,0,0,1-.34-.44,3.4,3.4,0,0,1-.26-.5,3.18,3.18,0,0,1,4.57-4,2.93,2.93,0,0,1,.49.38h0c.87.83,1.15,1,2.11,1.87l.84.74,6.79-7.29c2.87-3,7.45,1.37,4.58,4.4l-8.47,9.06-.43.45a3.19,3.19,0,0,1-4.43.19l0,0c-.22-.19-.44-.4-.66-.6-.52-.46-1.06-.94-1.61-1.41-1.26-1.09-2-1.69-3.17-2.87Zm9.43-22.09A19.86,19.86,0,1,1,60,90.74,19.86,19.86,0,0,1,79.88,70.88Z"></path></svg>',
			'desc'     => __( 'Adds wpForo SEO Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'antispam'      => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.0.0',
			'class'    => 'wpforo\classes\Moderation',
			'deps'     => [],
			'title'    => 'Spam Protection',
			'thumb'    => '<svg width="55px" version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 111.811 122.88" enable-background="new 0 0 111.811 122.88" xml:space="preserve"><g><path fill-rule="evenodd" clip-rule="evenodd" d="M55.713,0c20.848,13.215,39.682,19.467,55.846,17.989 c2.823,57.098-18.263,90.818-55.63,104.891C19.844,109.708-1.5,77.439,0.083,17.123C19.058,18.116,37.674,14.014,55.713,0L55.713,0 z M56.163,19.543c14.217,9.011,27.061,13.274,38.083,12.268c1.925,38.936-12.454,61.93-37.935,71.526 c-0.161-0.059-0.319-0.12-0.479-0.18V19.796L56.163,19.543L56.163,19.543z M55.735,7.055 c18.454,11.697,35.126,17.232,49.434,15.923c2.498,50.541-16.166,80.39-49.241,92.846C23.986,104.165,5.091,75.603,6.493,22.211 C23.29,23.091,39.768,19.46,55.735,7.055L55.735,7.055z"></path></g></svg>',
			'desc'     => __( 'Adds Spam Protection Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'akismet'       => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.0.0',
			'class'    => 'wpforo\classes\Moderation',
			'deps'     => [],
			'title'    => 'Akismet Antispam Integration',
			'thumb'    => WPFORO_URL . '/assets/images/dashboard/akismet.png',
			'desc'     => __( 'Adds Akismet Antispam Integration Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'rss'           => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.0.0',
			'class'    => '',
			'deps'     => [],
			'title'    => 'Forum Feed / RSS',
			'thumb'    => '<svg height="65px" style="enable-background:new 0 0 30.3 29.9;" version="1.1" viewBox="0 0 30.3 29.9" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><style type="text/css">.st33453{fill:none;stroke:#43a6df;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}.st22013{fill:none;stroke:#43a6df;stroke-width:1.1713;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}</style><g><circle class="st33453" cx="8.1" cy="20" r="2.4"></circle><path class="st33453" d="M5.4,13.4c0,0,9.7,0.6,9.3,9h3.4c0,0,0.5-11-12.7-12.1V13.4z"></path><path class="st33453" d="M5.7,7.3c0,0,15.3,1.7,14.8,14.9h3.4c0,0,0.8-16.4-18.2-18.1V7.3z"></path></g></svg>',
			'desc'     => __( 'Adds Forum Feed / RSS Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'social'        => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.0.0',
			'class'    => 'wpforo\classes\API',
			'deps'     => [],
			'title'    => 'Social Share',
			'thumb'    => '<svg height="60px" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 121.28 122.88"><path d="M50.51,46,64,37a27.93,27.93,0,1,1,6.68,10.72c-.43-.44-.85-.89-1.25-1.35L55.1,55.93a27.8,27.8,0,0,1,.76,6.5,28.37,28.37,0,0,1-.34,4.35L71.67,77.34A27.77,27.77,0,1,1,66.58,87L51.64,77.19a27.93,27.93,0,1,1-4-34.51A29.78,29.78,0,0,1,50.51,46ZM41.4,74.84a19,19,0,0,1-26.93,0L12.8,72.65,14,71.71c2.5-1.48,6.27-1.09,8.73-2.81A7.08,7.08,0,0,0,23.2,68c.23-.53.44-1.1.57-1.49a18.85,18.85,0,0,1-1.49-2.13L20.77,62a4.38,4.38,0,0,1-.86-2.2,1.69,1.69,0,0,1,.15-.79,1.42,1.42,0,0,1,.52-.6,1.55,1.55,0,0,1,.36-.19,38.65,38.65,0,0,1-.07-4.32,5.47,5.47,0,0,1,.19-1,5.78,5.78,0,0,1,2.55-3.26,8.37,8.37,0,0,1,2.14-.95c.48-.13-.41-1.67.09-1.72,2.4-.25,6.29,1.94,8,3.76a5.91,5.91,0,0,1,1.49,3.71l-.1,3.92h0a1.09,1.09,0,0,1,.8.82,3.4,3.4,0,0,1-.42,2.07h0l0,0-1.72,2.84a14.89,14.89,0,0,1-2.12,2.91l.23.33a10.22,10.22,0,0,0,1.12,1.45l0,0c2,1.41,6.81,1.75,8.67,2.78l.07,0,1.22,1a22.07,22.07,0,0,1-1.66,2.16ZM44,46.37a22.72,22.72,0,1,0,6.65,16.06A22.64,22.64,0,0,0,44,46.37Zm63.09,60.25c-5.76,6.39-21.5,6.83-27.43.52l0,0V105.8c0-3.44,5.72-3.5,8.77-5.33,2-1.21,1.68-2.43,1.67-4.44H86.85c-8.32,0-2.84.66-1.71-8.39,1.69-12.77,14.56-12.78,16.46,0C102.82,96.35,108,96,99.89,96H96.64c0,2.22-.36,3.35,1.94,4.6s8.5,1.88,8.5,5v1Zm2.33-27.73A22.72,22.72,0,1,0,116.06,95a22.64,22.64,0,0,0-6.65-16.06Zm-5.47-39.82a19,19,0,0,1-26.93,0l-1.67-2.18L76.56,36c2.5-1.48,6.27-1.09,8.73-2.81a7,7,0,0,0,.46-.88c.23-.53.43-1.1.57-1.49a18.56,18.56,0,0,1-1.5-2.13l-1.51-2.4a4.47,4.47,0,0,1-.86-2.2,1.69,1.69,0,0,1,.15-.79,1.37,1.37,0,0,1,.52-.6,1.43,1.43,0,0,1,.37-.19,36.43,36.43,0,0,1-.07-4.32,5.45,5.45,0,0,1,.18-1,5.76,5.76,0,0,1,2.56-3.26,7.9,7.9,0,0,1,2.14-1c.48-.14-.41-1.67.08-1.72,2.41-.25,6.29,1.94,8,3.76a5.82,5.82,0,0,1,1.48,3.7l-.09,3.93h0a1.1,1.1,0,0,1,.79.82,3.46,3.46,0,0,1-.41,2.07h0l0,0L96.37,28.4a14.49,14.49,0,0,1-2.12,2.91l.23.33a10.3,10.3,0,0,0,1.13,1.45s0,0,0,0c2,1.4,6.82,1.74,8.67,2.78l.08,0,1.22,1a22,22,0,0,1-1.67,2.15Zm2.59-27.2a22.72,22.72,0,1,0,6.65,16.06,22.64,22.64,0,0,0-6.65-16.06Z"></path></svg>',
			'desc'     => __( 'Adds Social Share Feature For Topics', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'revisions'     => [
			'base'     => false,
			'version'  => '1.0.0',
			'requires' => '1.0.0',
			'class'    => 'wpforo\modules\revisions\Revisions',
			'deps'     => [],
			'title'    => 'Post Preview and Auto Drafting',
			'thumb'    => '<svg version="1.1" height="53px" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 122.88 122.88" style="enable-background:new 0 0 122.88 122.88" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style><g><path class="st0" d="M79.7,31.87c-0.7-0.65-1.5-1-2.4-0.95c-0.9,0-1.7,0.35-2.35,1.05l-5.29,5.49L84.49,51.8l5.34-5.59 c0.65-0.65,0.9-1.5,0.9-2.4c0-0.9-0.35-1.75-1-2.35L79.7,31.87L79.7,31.87L79.7,31.87z M12.51,0h97.85c3.44,0,6.57,1.41,8.84,3.67 c2.27,2.27,3.67,5.4,3.67,8.84v97.85c0,3.44-1.41,6.57-3.67,8.84c-2.27,2.27-5.4,3.67-8.84,3.67H12.51c-3.44,0-6.57-1.41-8.84-3.67 c-2.27-2.27-3.67-5.4-3.67-8.84V12.51c0-3.44,1.41-6.57,3.67-8.84C5.94,1.41,9.07,0,12.51,0L12.51,0z M110.37,5.39H12.51 c-1.96,0-3.74,0.8-5.03,2.1c-1.29,1.29-2.1,3.08-2.1,5.03v97.85c0,1.96,0.8,3.74,2.1,5.03c1.29,1.29,3.08,2.1,5.03,2.1h97.85 c1.96,0,3.74-0.8,5.03-2.1c1.29-1.29,2.1-3.08,2.1-5.03V12.51c0-1.96-0.8-3.74-2.1-5.03C114.1,6.19,112.32,5.39,110.37,5.39 L110.37,5.39z M51.93,85.61c-1.95,0.65-3.95,1.25-5.89,1.9c-1.95,0.65-3.9,1.3-5.89,1.95c-4.64,1.5-7.19,2.35-7.74,2.5 c-0.55,0.15-0.2-2,0.95-6.49l3.7-14.13l0.3-0.32L51.93,85.61L51.93,85.61L51.93,85.61L51.93,85.61z M42.74,65.41l22.9-23.78 l14.83,14.28L57.33,79.99L42.74,65.41L42.74,65.41z"/></g></svg>',
			'desc'     => __( 'Adds Post Preview and Auto Drafting Feature For Topics And Posts', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
		'bookmarks'     => [
			'base'     => true,
			'version'  => '1.0.0',
			'requires' => '2.0.0',
			'class'    => 'wpforo\modules\bookmarks\Bookmarks',
			'deps'     => [],
			'title'    => 'Post Bookmark',
			'thumb'    => '<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="43.226px" height="43.226px" viewBox="0 0 43.226 43.226" style="enable-background:new 0 0 43.226 43.226;" xml:space="preserve"> <g> <g> <path d="M37.119,28.389c0-8.208,0-16.417,0-24.624c0-5.556-12.92-3.32-16.004-3.32c-3.796,0-11.869-1.572-14.403,2.354 c-0.429,0.663-0.566,1.33-0.575,1.363C6.13,4.187,6.12,4.202,6.12,4.222c0,6.907,0,13.817,0,20.727 c0,2.296-1.792,17.566,1.371,18.212c0.472,0.098,0.975,0.081,1.498-0.021c0.774-0.152,1.328-0.399,1.288-0.361 c-0.041,0.037-0.037,0.045,0.008,0.017c0.024-0.016,0.052-0.029,0.078-0.051c0,0,0.026-0.02,0.057-0.043 c0.032-0.024,0.648-0.3,1.33-0.699c3.325-1.95,6.837-5.704,8.679-7.102c3.335-2.534,10.157,10.289,14.973,7.901 C39.006,41.012,37.119,31.631,37.119,28.389z M32.341,38.072c-0.556,0.561-1.275,0.811-1.607,0.557 c-0.331-0.253-0.149-0.91,0.405-1.473c0.278-0.281,0.557-0.561,0.834-0.842c0.557-0.562,0.956-1.042,0.892-1.071 c-0.065-0.029-0.561,0.412-1.104,0.985c-0.258,0.271-0.515,0.542-0.771,0.811c-0.543,0.572-1.112,0.939-1.271,0.818 c-0.097-0.072-0.191-0.146-0.286-0.218c-0.187-0.144-0.372-0.283-0.558-0.426c-0.307-0.233-0.184-0.818,0.271-1.307 c0.456-0.487,0.556-0.81,0.248-0.66c-0.188,0.092-0.365,0.199-0.529,0.323c0,0-0.241,0.184-0.537,0.409 c-0.298,0.227-1.046,0.02-1.699-0.424c-2.529-1.725-5.066-2.683-8.339-0.69c-2.322,1.412-4.44,3.364-6.604,5.006 c-2.647,2.012-2.805,2.818-2.805-0.311c0-0.388,0-0.773,0-1.16c0-0.641,0.449-1.615,1.004-2.178 c1.479-1.5,2.963-3.004,4.455-4.516c0.556-0.562,0.533-0.583-0.047-0.05c-0.385,0.353-0.773,0.71-1.173,1.076 c-1.064,0.986-2.127,1.969-3.189,2.953c-0.579,0.536-1.05,0.37-1.05-0.373c0-0.742,0.473-1.777,1.056-2.312 c2.015-1.845,4.029-3.688,6.036-5.539c0.58-0.534,1.051-1.526,1.051-2.218c0-0.688-0.469-0.812-1.048-0.273 c-2.017,1.872-4.033,3.744-6.049,5.617c-0.578,0.538-1.046,0.385-1.046-0.343s0.468-1.754,1.044-2.292 c1.998-1.863,4.022-3.753,6.055-5.649c0.576-0.539,1.044-1.116,1.044-1.289s-0.453,0.142-1.012,0.698 c-1.746,1.747-3.775,3.781-6.123,6.136c-0.557,0.56-1.008,0.371-1.008-0.419c0-0.634,0-1.265,0-1.897 c0-0.79,0.445-1.89,0.994-2.457c2.066-2.133,4.119-4.248,6.151-6.341c0.551-0.565,0.997-1.39,0.997-1.839 c0-0.451-0.18-0.644-0.403-0.431c-0.131,0.126-0.264,0.252-0.403,0.386c-2.167,2.158-4.27,4.25-6.322,6.293 c-0.56,0.558-1.014,0.367-1.014-0.422c0-0.94,0-1.878,0-2.816c0-0.79,0.455-1.881,1.017-2.435c2.036-2.011,4.07-4.02,6.108-6.026 c0.562-0.555,1.017-1.604,1.017-2.348c0-0.742-0.445-0.888-0.995-0.319c-2.056,2.115-4.102,4.232-6.153,6.348 c-0.549,0.566-0.994,0.548-0.994-0.045c0-0.591,0.453-1.521,1.008-2.085c0.789-0.8,2.07-2.104,3.973-4.042 c0.814-0.838,1.599-1.645,2.365-2.433c0.55-0.565,1.017-1.072,1.042-1.129c0.026-0.059-0.398,0.354-0.945,0.923 c-2.484,2.561-5.072,5.228-6.454,6.657C9.32,11.54,8.881,11.92,8.881,11.816s0.448-0.646,0.999-1.211 c3.111-3.181,5.81-5.928,7.886-8.019c0.556-0.56,0.428-1.013-0.287-1.013c-0.715,0-1.749,0.448-2.314,1.002 c-1.738,1.707-3.496,3.436-5.264,5.173c-0.562,0.556-1.02,0.364-1.02-0.426c0-0.516,0-1.032,0-1.547 c0-0.791,0.451-1.887,1.008-2.446c0.246-0.246,0.491-0.493,0.737-0.739c0.556-0.561,0.854-1.017,0.662-1.017h-0.345 c-0.105,0-0.21,0.007-0.312,0.02c-0.172,0.021-0.573,0.317-0.896,0.646C9.412,2.566,9.4,2.181,10.073,1.77 c1.258-0.77,3.174-0.236,5.225-0.236c4.017,0,8.034,0,12.052,0c2.18,0,7.008-0.868,7.008,2.966c0,2.308,0,4.613,0,6.92 c0,7.59,0,15.18,0,22.769c0,0.348,0,0.697,0,1.046c0,0.578-0.031,1.021-0.07,0.994c-0.037-0.029-0.518,0.401-1.076,0.964 C32.921,37.488,32.63,37.78,32.341,38.072z M34.353,41.38c-0.056-0.043-0.118-0.094-0.196-0.151 c-0.185-0.141-0.367-0.279-0.552-0.419c-0.304-0.23-0.261-0.72,0.101-1.092c0.36-0.371,0.653-0.209,0.653,0.359 c0,0.345,0,0.69,0,1.034c0,0.081,0.022,0.158,0.056,0.229C34.469,41.459,34.459,41.468,34.353,41.38z"/> <path d="M30.369,1.578c-0.242,0-0.46,0.021-0.487,0.048c-0.026,0.025,0.148,0.046,0.394,0.046c0.244,0,0.464-0.02,0.488-0.047 C30.788,1.6,30.612,1.578,30.369,1.578z"/> <path d="M22.525,1.578c-0.791,0-1.451,0.021-1.477,0.047c-0.024,0.026,0.596,0.047,1.386,0.047h0.026 c0.79,0,1.451-0.02,1.478-0.047c0.025-0.024-0.594-0.047-1.383-0.047H22.525z"/> <path d="M35.924,42.904c0.212-0.234,0.104-0.323-0.105-0.107C35.607,43.015,35.71,43.14,35.924,42.904z"/> <path d="M18.671,1.95c0.063-0.037,0.186-0.13,0.269-0.215c0.084-0.087,0.126-0.157,0.094-0.157c-0.032,0-0.15,0.099-0.268,0.218 C18.65,1.915,18.607,1.984,18.671,1.95z"/> </g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> </svg>',
			'desc'     => __( 'Adds Post Bookmark Feature For Posts', 'wpforo' ),
			'url'      => 'https://wpforo.com',
		],
	];

	if( ! is_null( $base ) ) {
		$modules = array_filter( $modules, function( $module ) use ( $base ) {
			return $module['base'] === $base;
		} );
	}

	return $modules;
}

function wpforo_get_addons_info( $base = null ): array {
	$addons = [
		'wpforo-user-custom-fields'      => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-user-custom-fields/wpforo-ucf.php' ),
			'base'     => true,
			'version'  => '3.0.0',
			'requires' => '2.0.0',
			'class'    => 'WpforoUcf',
			'deps'     => [],
			'title'    => 'User Custom Fields',
			'thumb'    => WPFORO_URL . '/assets/addons/ucf/header.png',
			'desc'     => 'Advanced user profile builder system. Allows to add new fields and manage profile. Creates custom Registration, Account, Member Search forms.',
			'url'      => 'https://gvectors.com/product/wpforo-user-custom-fields/',
		],
		'wpforo-topic-custom-fields'     => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-topic-custom-fields/wpforotcf.php' ),
			'base'         => false,
			'version'      => '3.0.1',
			'requires'     => '2.0.0',
			'class'        => 'wpForoTcf',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoTcf' ) && function_exists( 'wpforotcf_activation' ) && version_compare( WPFORO_VERSION, WPFOROTCF_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforotcf_activation();
				}
			},
			'title'        => 'Topic Custom Fields',
			'thumb'        => WPFORO_URL . '/assets/addons/tcf/header.png',
			'desc'         => 'Allows to create topic custom fields and manage topic form layout with a form builder. Adds topic search options by custom fields',
			'url'          => 'https://gvectors.com/product/wpforo-topic-custom-fields/',
		],
		'wpforo-private-messages'        => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-private-messages/wpforopm.php' ),
			'base'         => true,
			'version'      => '3.0.0',
			'requires'     => '2.0.0',
			'class'        => 'wpForoPMs',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoPMs' ) && function_exists( 'wpforopm_activation' ) && version_compare( WPFORO_VERSION, WPFOROPM_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforopm_activation();
				}
			},
			'title'        => 'Private Messages',
			'thumb'        => WPFORO_URL . '/assets/addons/pm/header.png',
			'desc'         => 'Provides a safe way to communicate directly with other members. Messages are private and can only be viewed by conversation participants.',
			'url'          => 'https://gvectors.com/product/wpforo-private-messages/',
		],
		'wpforo-topic-prefix'            => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-topic-prefix/wpforotpx.php' ),
			'base'         => false,
			'version'      => '3.0.0',
			'requires'     => '2.0.0',
			'class'        => 'wpForoTopicPrefix',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoTopicPrefix' ) && function_exists( 'wpforotpx_activation' ) && version_compare( WPFORO_VERSION, WPFOROTPX_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforotpx_activation();
				}
			},
			'title'        => 'Topic Prefix & Tag Manager',
			'thumb'        => WPFORO_URL . '/assets/addons/prefix/header.png',
			'desc'         => 'Allows you to create topic prefixes and prefix groups to categorize topics. Also, it allows you to add, edit, delete topic tags and convert them to prefixes.',
			'url'          => 'https://gvectors.com/product/wpforo-topic-prefix/',
		],
		'wpforo-advanced-reactions'      => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo_reactions/Main.php' ),
			'base'     => false,
			'version'  => '3.0.0',
			'requires' => '2.3.2',
			'class'    => '\wpforo_reactions\Main',
			'deps'     => [],
			'title'    => 'Advanced Reactions',
			'thumb'    => WPFORO_URL . '/assets/addons/reactions/header.png',
			'desc'     => 'Allows users to add new types of reactions and provides a frontend dialog to view each post\'s currently reacted users',
			'url'      => 'https://gvectors.com/product/wpforo_reactions/',
		],
		'wpforo-advanced-attachments'    => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-advanced-attachments/wpforoattach.php' ),
			'base'         => false,
			'version'      => '3.0.1',
			'requires'     => '2.0.0',
			'class'        => 'wpForoAttachments',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoAttachments' ) && function_exists( 'wpforoattach_activation' ) && version_compare( WPFORO_VERSION, WPFOROATTACH_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforoattach_activation();
				}
			},
			'title'        => 'Advanced Attachments',
			'thumb'        => WPFORO_URL . '/assets/addons/attachments/header.png',
			'desc'         => 'Adds an advanced file attachment system to forum. AJAX powered media uploading and displaying system with user specific library.',
			'url'          => 'https://gvectors.com/product/wpforo-advanced-attachments/',
		],
		'wpforo-embeds'                  => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-embeds/wpforoembeds.php' ),
			'base'     => false,
			'version'  => '3.0.0',
			'requires' => '2.0.0',
			'class'    => 'wpForoEmbeds',
			'deps'     => [],
			'title'    => 'Embeds',
			'thumb'    => WPFORO_URL . '/assets/addons/embeds/header.png',
			'desc'     => 'Allows to embed hundreds of video, social network, audio and photo content providers in forum topics and posts.',
			'url'      => 'https://gvectors.com/product/wpforo-embeds/',
		],
		'wpforo-memberpress'             => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-memberpress/wpforo_mempress.php' ),
			'base'         => true,
			'version'      => '1.0.0',
			'requires'     => '2.2.2',
			'class'        => 'wpForoMemberPress',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoMemberPress' ) && function_exists( 'wpforomp_activation' ) && version_compare( WPFORO_VERSION, WPFOROMP_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforomp_activation();
				}
			},
			'title'        => 'MemberPress Integration',
			'thumb'        => WPFORO_URL . '/assets/addons/wpforo-memberpress/header.png',
			'desc'         => 'Allows to setup membership access to your forums through MemberPress membership plugin.',
			'url'          => 'https://gvectors.com/product/wpforo-memberpress/',
		],
		'wpforo-groups'                  => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-groups-membership/wpforo_groups.php' ),
			'base'         => true,
			'version'      => '1.0.0',
			'requires'     => '2.2.2',
			'class'        => 'wpForoGroups',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoGroups' ) && function_exists( 'wpforomp_activation' ) && version_compare( WPFORO_VERSION, WPFOROGRP_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforomp_activation();
				}
			},
			'title'        => 'Groups Plugin Integration',
			'thumb'        => WPFORO_URL . '/assets/addons/wpforo-groups/header.png',
			'desc'         => 'Allows to setup membership access to your forums through Groups plugin.',
			'url'          => 'https://gvectors.com/product/wpforo-groups-membership/',
		],
		'wpforo-suremembers'             => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-suremembers/wpforo_surmem.php' ),
			'base'         => true,
			'version'      => '1.0.0',
			'requires'     => '2.2.2',
			'class'        => 'wpForoSureMembers',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoSureMembers' ) && function_exists( 'wpforosm_activation' ) && version_compare( WPFORO_VERSION, WPFOROSM_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforosm_activation();
				}
			},
			'title'        => 'SureMembers Integration',
			'thumb'        => WPFORO_URL . '/assets/addons/wpforo-suremembers/header.png',
			'desc'         => 'Allows to setup membership access to your forums through SureMembers membership plugin.',
			'url'          => 'https://gvectors.com/product/wpforo-suremembers/',
		],
		'wpforo-paid-membership-pro'     => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-paid-membership-pro/wpforo_mempro.php' ),
			'base'         => true,
			'version'      => '1.0.0',
			'requires'     => '2.2.2',
			'class'        => 'wpForoPaidMembershipPro',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoPaidMembershipPro' ) && function_exists( 'wpforopmp_activation' ) && version_compare( WPFORO_VERSION, WPFOROPMP_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforopmp_activation();
				}
			},
			'title'        => 'Paid Membership Pro Integration',
			'thumb'        => WPFORO_URL . '/assets/addons/wpforo-paid-membership-pro/header.png',
			'desc'         => 'Allows to setup membership access to your forums through Paid Membership Pro plugin.',
			'url'          => 'https://gvectors.com/product/wpforo-paid-membership-pro/',
		],
		'wpforo-woocommerce-memberships' => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-woocommerce-memberships/wpforo_woomem.php' ),
			'base'         => true,
			'version'      => '3.0.0',
			'requires'     => '2.0.0',
			'class'        => 'wpForoWooMembers',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoWooMembers' ) && function_exists( 'wpforowm_activation' ) && version_compare( WPFORO_VERSION, WPFOROWM_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforowm_activation();
				}
			},
			'title'        => 'WooCommerce Memberships Integration',
			'thumb'        => WPFORO_URL . '/assets/addons/wpforo-woocomerce-memberships/header.png',
			'desc'         => 'Allows to setup membership access to your forums and topics through WooCommerce Memberships plugin.',
			'url'          => 'https://gvectors.com/product/wpforo-woocommerce-memberships/',
		],
		'wpforo-mentioning'              => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo_mentioning/Main.php' ),
			'base'     => false,
			'version'  => '3.0.0',
			'requires' => '2.2.9',
			'class'    => '\wpforo_mentioning\Main',
			'deps'     => [],
			'title'    => 'User Mentioning',
			'thumb'    => WPFORO_URL . '/assets/addons/wpforo-mentioning/header.png',
			'desc'     => 'Opens pop-up window to select users for mentioning by @nickname.',
			'url'      => 'https://gvectors.com/product/wpforo_mentioning/',
		],
		'wpforo-voice-posting'           => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-voice-posting/wpforoVoicePosting.php' ),
			'base'     => false,
			'version'  => '3.0.0',
			'requires' => '2.2.8',
			'class'    => 'wpforoVoicePosting',
			'deps'     => [],
			'title'    => 'Voice Posting',
			'thumb'    => WPFORO_URL . '/assets/addons/wpforo-voice-posting/header.png',
			'desc'     => 'Allows to record and attach voice messages to forum posts and private messages.',
			'url'      => 'https://gvectors.com/product/wpforo-voice-posting/',
		],
		'wpforo-cross-posting'           => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-cross-posting/wpForoCrossPosting.php' ),
			'base'     => false,
			'version'  => '3.0.0',
			'requires' => '2.0.0',
			'class'    => 'wpForoCrossPosting',
			'deps'     => [],
			'title'    => '"Forum - Blog" Cross Posting',
			'thumb'    => WPFORO_URL . '/assets/addons/cross/header.png',
			'desc'     => 'Blog to Forum and Forum to Blog content synchronization. Blog posts with Forum topics and Blog comments with Forum replies.',
			'url'      => 'https://gvectors.com/product/wpforo-cross-posting/',
		],
		'wpforo-ad-manager'              => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-ad-manager/wpforoad.php' ),
			'base'         => false,
			'version'      => '3.0.1',
			'requires'     => '2.0.0',
			'class'        => 'wpForoAD',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoAD' ) && function_exists( 'wpforoad_activation' ) && version_compare( WPFORO_VERSION, WPFOROAD_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforoad_activation();
				}
			},
			'title'        => 'Ads Manager',
			'thumb'        => WPFORO_URL . '/assets/addons/ad-manager/header.png',
			'desc'         => 'Ads Manager is a powerful yet simple advertisement management system. Allows you to add adverting banners between forums, topics and posts.',
			'url'          => 'https://gvectors.com/product/wpforo-ad-manager/',
		],
		'wpforo-polls'                   => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-polls/wpforopoll.php' ),
			'base'         => false,
			'version'      => '3.0.0',
			'requires'     => '2.0.0',
			'class'        => 'wpForoPoll',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoPoll' ) && function_exists( 'wpforopoll_activation' ) && version_compare( WPFORO_VERSION, WPFOROPOLL_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforopoll_activation();
				}
			},
			'title'        => 'Polls',
			'thumb'        => WPFORO_URL . '/assets/addons/polls/header.png',
			'desc'         => 'wpForo Polls is a complete addon to help forum members create, vote and manage polls effectively. Comes with poll specific permissions and settings.',
			'url'          => 'https://gvectors.com/product/wpforo-polls/',
		],
		'wpforo-emoticons'               => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-emoticons/wpforosmile.php' ),
			'base'         => false,
			'version'      => '3.0.0',
			'requires'     => '2.0.0',
			'class'        => 'wpForoSmiles',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoSmiles' ) && function_exists( 'wpforosmile_activation' ) && version_compare( WPFORO_VERSION, WPFOROSMILE_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpforosmile_activation();
				}
			},
			'title'        => 'wpForo Emoticons',
			'thumb'        => WPFORO_URL . '/assets/addons/wpforo-emoticons/header.png',
			'desc'         => 'Adds awesome Sticker and Emoticons packs to editor. Allows to create new custom emoticons packs.',
			'url'          => 'https://gvectors.com/product/wpforo-emoticons/',
		],
		'wpforo-tenor'                   => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-tenor/wpforotenor.php' ),
			'base'     => false,
			'version'  => '3.0.0',
			'requires' => '2.0.0',
			'class'    => 'wpForoTenor',
			'deps'     => [],
			'title'    => 'Tenor GIFs Integration',
			'thumb'    => WPFORO_URL . '/assets/addons/tenor/header.png',
			'desc'     => 'Adds Tenor [GIF] button and opens popup where you can search for gifs and insert them in topic, post and private message content.',
			'url'      => 'https://gvectors.com/product/wpforo-tenor/',
		],
		'wpforo-giphy'                   => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-giphy/wpforogiphy.php' ),
			'base'     => false,
			'version'  => '3.0.0',
			'requires' => '2.0.0',
			'class'    => 'wpForoGiphy',
			'deps'     => [],
			'title'    => 'GIPHY Integration',
			'thumb'    => WPFORO_URL . '/assets/addons/giphy/header.png',
			'desc'     => 'Adds GIPHY [GIF] button and opens popup where you can search for gifs and insert them in topic, post and private message content.',
			'url'      => 'https://gvectors.com/product/wpforo-giphy/',
		],
		'wpforo-mycred'                  => [
			'ABSPATH'  => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-mycred/wpforo-mc.php' ),
			'base'     => true,
			'version'  => '3.0.0',
			'requires' => '2.0.0',
			'class'    => 'myCRED_Hook_wpForo',
			'deps'     => [],
			'title'    => 'MyCRED Integration',
			'thumb'    => WPFORO_URL . '/assets/addons/mycred/header.png',
			'desc'     => 'Awards myCRED points for forum activity. Integrates myCRED Badges and Ranks. Converts wpForo topic and posts, likes to myCRED points.',
			'url'      => 'https://gvectors.com/product/wpforo-mycred/',
		],
		'wpforo-syntax-highlighter'      => [
			'ABSPATH'      => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-syntax-highlighter/wpForoSyntaxHighlighter.php' ),
			'base'         => false,
			'version'      => '3.0.0',
			'requires'     => '2.0.0',
			'class'        => 'wpForoSyntaxHighlighter',
			'deps'         => [],
			'install_func' => function() {
				if( class_exists( 'wpForoSyntaxHighlighter' ) && function_exists( 'wpForoSyntaxActivation' ) && version_compare( WPFORO_VERSION, WPFOROSYNTAX_WPFORO_REQUIRED_VERSION, '>=' ) ) {
					wpForoSyntaxActivation();
				}
			},
			'title'        => 'Syntax Highlighter',
			'thumb'        => WPFORO_URL . '/assets/addons/syntax/header.png',
			'desc'         => 'Syntax highlighting for forum posts, automatic language detection and multi-language code highlighting.',
			'url'          => 'https://gvectors.com/product/wpforo-syntax-highlighter/',
		],

	];

	if( ! is_null( $base ) ) {
		$addons = array_filter( $addons, function( $addon ) use ( $base ) {
			return $addon['base'] === $base;
		} );
	}

	return $addons;
}

function wpforo_get_ajax_actions_list() {
	$nonces = [];
	global $wp_filter;
	if( WPF()->current_userid ) {
		foreach( $wp_filter as $key => $value ) {
			if( strpos( (string) $key, 'wp_ajax_wpf' ) === 0 ) $nonces[] = preg_replace( '#^wp_ajax_#iu', '', (string) $key, 1 );
		}
	} else {
		foreach( $wp_filter as $key => $value ) {
			if( strpos( (string) $key, 'wp_ajax_nopriv_wpf' ) === 0 ) $nonces[] = preg_replace( '#^wp_ajax_nopriv_#iu', '', (string) $key, 1 );
		}
	}

	return array_unique( (array) apply_filters( 'wpforo_get_ajax_actions_list', $nonces ) );
}

function wpforo_generate_ajax_nonces() {
	$nonces = [];
	foreach( wpforo_get_ajax_actions_list() as $action ) {
		$nonces[ $action ] = wp_create_nonce( $action );
	}

	return $nonces;
}

function wpforo_verify_form( $action = 'wpforo_verify_form', $query_arg = '_wpfnonce', $mode = 'full' ) {
	wpforo_verify_nonce( $action, $mode, $query_arg );
	do_action( 'wpforo_verify_form_end' );
}

function wpforo_verify_nonce( $action = 'wpforo_verify_form', $mode = 'ajax', $query_arg = '_wpfnonce' ) {
	if( $mode === 'full' && apply_filters( 'wpforo_check_request', true ) ) {
		if( ! wp_verify_nonce( wpfval( $_REQUEST, $query_arg ), $action ) ) {
			wpforo_phrase( 'Sorry, something is wrong with your data.' );
			exit();
		}
	} elseif( $mode === 'ajax' ) {
		check_ajax_referer( $action, $query_arg );
	}
	if( apply_filters( 'wpforo_check_referer', true ) && in_array( $mode, [ 'ajax', 'full', 'ref' ] ) ) {
		if( ! wpfval( $_SERVER, 'HTTP_REFERER' ) ) {
			exit( 'Error 2252 | Please contact the forum administrator.' );
		}
		if( wpforo_is_url_external( $_SERVER['HTTP_REFERER'] ) ) {
			exit( 'Error 2253 | Please contact the forum administrator.' );
		}
	}
	do_action( 'wpforo_verify_nonce_end' );
}

/**
 * @param string $path
 * @param string|null $route
 * @param string|null $scheme
 *
 * @return string
 */
function wpforo_url( $path = '', $route = null, $scheme = null ) {
	if( $route ) $route = wpforo_settings_get_slug( $route );
	if( is_null( $route ) ) $route = WPF()->board->full_route;
	$path = trim( trim( (string) $route, '/\\' ) . '/' . trim( (string) $path, '/\\' ), '/\\' );

	if( function_exists( 'pll_default_language' ) && function_exists( 'PLL' ) ) {
		if( ! PLL()->options['hide_default'] && $lng = pll_default_language() ) $path = $lng . '/' . $path;
	}

	$url = home_url( $path, $scheme );
	//-START- check is url maybe WordPress home
	$maybe_home_url = trim( preg_replace( '#/?index\.php/?(\?.*)?$#isu', '', (string) $url ), '/\\' );
	$home_url       = trim( home_url( '', $scheme ), '/\\' );
	if( $maybe_home_url === $home_url ) $url = preg_replace( '#index\.php/?#isu', '', (string) $url, 1 );

	//-END- check is url maybe WordPress home

	$url = apply_filters( 'wpforo_url', $url, $path, $route, $scheme );

	return WPF()->user_trailingslashit( $url );
}

/**
 * @param string $path
 * @param string|null $scheme
 *
 * @return string
 */
function wpforo_home_url( $path = '', $scheme = null ) {
	if( strpos( (string) $path, 'http' ) === 0 ) {
		$path = wpforo_get_url_query_vars_str( $path );
		$path = preg_replace( '#index\.php/?#isu', '', (string) $path, 1 );
		$path = preg_replace( '#^/?' . preg_quote( trim( (string) WPF()->board->route, '/\\' ) ) . '#isu', '', (string) $path, 1 );
	}

	return wpforo_url( $path, null, $scheme );
}

function wpforo_is_ajax() {
	return defined( 'DOING_AJAX' ) && DOING_AJAX;
}

function wpforo_is_admin( $url = '' ) {
	$url = trim( (string) $url );
	if( $url ) return strpos( (string) $url, trim( admin_url(), '/' ) ) === 0 || strpos( $url, trim( self_admin_url(), '/' ) ) === 0;

	return is_admin() && ! wpforo_is_ajax();
}

function _is_wpforo_page( $url = '' ) {
	if( ! $url ) $url = wpforo_get_request_uri();
	$result = ( ! ( wpforo_is_admin( $url ) || ( is_wpforo_exclude_url( $url ) && ! is_wpforo_url( $url ) && ! is_wpforo_shortcode_page( $url ) ) ) && ( is_wpforo_url( $url ) || is_wpforo_shortcode_page( $url ) ) );

	return apply_filters( 'is_wpforo_page', $result, $url );
}

function is_wpforo_page( $url = '' ) {
	return wpforo_ram_get( '_is_wpforo_page', $url );
}

function _is_wpforo_exclude_url( $url = '' ) {
	$result = false;
	if( ! $url ) $url = wpforo_get_request_uri();
	$url = urldecode( (string) $url );
	$url = preg_replace( '#/page/\d*/?$#iu', '', (string) $url );
	if( $current_url = wpforo_get_url_query_vars_str( $url ) ) {
		if( preg_match( '#^/?(?:([^\s/?&=<>:\'\"*\\\|]*/)(?1)*)?[^\s/?&=<>:\'\"*\\\|]+\.(?:php|js|css|jpe?g|png|gif|webp|svg|bmp|tiff)/?(?:\?[^/]*)?$#iu', $current_url ) ) {
			$result = true;
		} elseif( WPF()->board->get_current( 'is_standalone' ) && ( $excld_urls = WPF()->board->get_current( 'excld_urls' ) ) ) {
			foreach( $excld_urls as $excld_url ) {
				$excld_url = urldecode( (string) $excld_url );
				if( wpforo_is_url_internal( $excld_url ) ) {
					$excld_url = wpforo_get_url_query_vars_str( $excld_url );
					$pattern   = preg_quote( $excld_url );
					$pattern   = str_replace( [ '/\*', '\*' ], [ '/?[^\r\n\t\s\0]*?', '[^\r\n\t\s\0]*?' ], $pattern );
					if( preg_match( '#^' . $pattern . '$#iu', $current_url ) ) {
						$result = true;
						break;
					}
				}
			}
		}
	}

	return $result;
}

function is_wpforo_exclude_url( $url = '' ) {
	return wpforo_ram_get( '_is_wpforo_exclude_url', $url );
}

function _is_wpforo_url( $url = '' ) {
	$result = false;
	if( ! wpforo_is_admin( $url ) ) {
		if( WPF()->board->get_current( 'is_standalone' ) && ! is_wpforo_exclude_url( $url ) ) {
			$result = true;
		} else {
			$current_url = wpforo_get_url_query_vars_str( $url );
			$current_url = preg_replace( '#/?\?.*$#isu', '', $current_url );
			foreach( WPF()->board->routes as $route ) {
				if( $route && ( $current_url === $route || strpos( (string) $current_url, $route . '/' ) === 0 ) ) {
					$result = true;
					break;
				}
			}
		}
	}

	return $result;
}

function is_wpforo_url( $url = '' ) {
	return wpforo_ram_get( '_is_wpforo_url', $url );
}

/**
 * @param string $url
 *
 * @return bool
 */
function _is_wpforo_shortcode_page( $url = '' ) {
	$result = ! wpforo_is_admin( $url ) && ! is_wpforo_url( $url ) && has_shortcode( wpforo_get_wp_post_content( $url ), 'wpforo' );

	return apply_filters( 'wpforo_is_shortcode_page', $result, $url );
}

function is_wpforo_shortcode_page( $url = '' ) {
	return wpforo_ram_get( '_is_wpforo_shortcode_page', $url );
}

function _wpforo_get_wp_post_content( $url = '' ) {
	if( ! $url ) $url = wpforo_get_request_uri();
	$post_content = '';
	global $post;
	if( $url === wpforo_get_request_uri() && is_a( $post, 'WP_Post' ) ) {
		$post_content = $post->post_content;
	} elseif( $postid = wpforo_wp_url_to_postid( $url ) ) {
		$post_content = get_post_field( 'post_content', $postid );
	}

	return $post_content;
}

function wpforo_get_wp_post_content( $url = '' ) {
	return wpforo_ram_get( '_wpforo_get_wp_post_content', $url );
}


/**
 * @param string $text
 * @param string $url
 *
 * @return array|string
 */
function get_wpforo_shortcode_atts( $text = '', $url = '' ) {
	if( ! $text ) $text = wpforo_get_wp_post_content( $url );
	if( preg_match( '#\[[\r\n\t\s\0]*wpforo[\r\n\t\s\0]*([^\[\]]*?)]#iu', $text, $match ) ) {
		return shortcode_parse_atts( $match[1] );
	}

	return '';
}

function wpforo_get_url_query_vars_str( $url = '' ) {
	if( ! $url ) $url = wpforo_get_request_uri();
	$home_url    = preg_replace( '#/?\?.*$#isu', '', home_url( '/' ) );
	$current_url = preg_replace( '#https?://[^/\?]+/?#isu', '', $url );
	$site_url    = preg_replace( '#https?://[^/\?]+/?#isu', '', $home_url );
	if( strpos( (string) $current_url, '/' ) === false && $current_url === trim( (string) $site_url, '/' ) ) return '';
	$current_url = preg_replace( '#^/?' . preg_quote( $site_url ) . '(?:/?index\.php/?)?#isu', '', $current_url, 1 );
	$current_url = preg_replace( '#^/?' . preg_quote( wpforo_get_query_var_lang() ) . '/#isu', '', $current_url, 1 );
	$current_url = preg_replace( '#^[\s\0/]*(.*?)[\s\0/]*$#isu', '$1', $current_url );

	return wpforo_fix_url( $current_url );
}

function wpforo_get_url_route( $url = '' ) {
	$route          = '';
	$query_vars_str = preg_replace( '#/?\?.*$#isu', '', wpforo_get_url_query_vars_str( (string) $url ) );
	$query_vars     = array_values( array_filter( explode( '/', trim( (string) $query_vars_str, '/' ) ) ) );
	if( array_key_exists( 0, $query_vars ) && in_array( $query_vars[0], WPF()->board->routes ) ) $route = $query_vars[0];

	return $route;
}

function wpforo_dir_size( $directory ) {
	$size = 0;
	if( is_dir( $directory ) && class_exists( 'RecursiveIteratorIterator' ) && class_exists( 'RecursiveDirectoryIterator' ) ) {
		foreach( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $directory ) ) as $file ) $size += $file->getSize();
	}

	return $size;
}

function wpforo_make_hidden_fields_from_url( $url = '', $echo = true ) {
	if( ! $url ) $url = wpforo_get_request_uri();
	$return = '';
	if( $url_query = parse_url( $url, PHP_URL_QUERY ) ) {
		parse_str( $url_query, $url_query_arr );
		if( ! empty( $url_query_arr ) ) {
			foreach( $url_query_arr as $key => $value ) {
				$return .= '<input type="hidden" name="' . $key . '" value="' . $value . '">';
			}
		}
	}
	if( ! $echo ) return $return;
	echo $return;
}

/**
 * Returns merged arguments array from defined and default arguments.
 *
 * @param mixed $args
 * @param array $default
 *
 * @return array
 */
function wpforo_parse_args( $args, $default = [] ) {
	if( is_object( $args ) ) {
		$defined = get_object_vars( $args );
	} elseif( is_array( $args ) ) {
		$defined = $args;
	} elseif( preg_match( '|^[\d,\s]+$|', (string) $args ) ) {
		$defined = explode( ',', trim( (string) $args ) );
	} elseif( is_integer( $args ) || is_float( $args ) ) {
		$defined[0] = $args;
	} elseif( is_serialized( $args ) ) {
		// SECURITY: refuse to instantiate any class — only scalars and arrays
		// of scalars are recovered. Blocks PHP Object Injection via widget
		// AJAX (forumids etc.) and any other caller that may receive
		// attacker-controlled strings.
		$defined = unserialize( $args, [ 'allowed_classes' => false ] );
		if( ! is_array( $defined ) ) $defined = (array) $defined;
	} elseif( strpos( (string) $args, '=' ) !== false ) {
		parse_str( $args, $defined );
	} else {
		$defined = (array) $args;
	}
	if( ! empty( $default ) ) {
		return array_merge( $default, $defined );
	} else {
		return $defined;
	}
}

/**
 * Detects serialized data
 *
 * @param string
 *
 * @return    boolean
 * @since    1.0.0
 *
 */
if( ! function_exists( 'is_serialized' ) ) {
	function is_serialized( $value ) {
		if( $value == '' ) return false;
		$value = trim( (string) $value );
		// SECURITY: the very act of detecting a serialized payload must not
		// instantiate classes — @unserialize() with the default options runs
		// __wakeup() / __destruct() for any embedded object even when the
		// caller only wanted to test the format. Limit to scalars/arrays.
		$chsd  = @unserialize( $value, [ 'allowed_classes' => false ] );
		if( $chsd !== false || $value === 'b:0;' ) {
			return true;
		} else {
			return false;
		}
	}
}

function wpforo_get_request_uri( $with_port = false, $get_referer_when_ajax = true ) {
	if( $get_referer_when_ajax && wpforo_is_ajax() ) {
		if( $referer = wpfval( $_REQUEST, 'referer' ) ) {
			$referer = preg_replace( '#\#[^/?&]*$#iu', '', (string) $referer );

			return esc_url_raw( $referer );
		}
		if( $referer = wpfval( $_SERVER, 'HTTP_REFERER' ) ) {
			$url = preg_replace( '#\#[^/?&]*$#iu', '', (string) $referer );

			return esc_url_raw( $url );
		}
	}
	$s           = is_ssl() ? 's' : '';
	$sp          = strtolower( (string) wpfval( $_SERVER, 'SERVER_PROTOCOL' ) );
	$protocol    = substr( $sp, 0, strpos( (string) $sp, "/" ) ) . $s;
	$server_port = intval( wpfval( $_SERVER, 'SERVER_PORT' ) );
	$port        = $server_port === 80 ? "" : ":" . $server_port;
	$host        = preg_replace( '#^www\.#iu', '', (string) wpfval( $_SERVER, 'HTTP_HOST' ) );
	if( strpos( home_url(), 'www.' ) !== false ) $host = 'www.' . $host;
	$requri = wpfval( $_SERVER, 'UNENCODED_URL' ) ?: wpfval( $_SERVER, 'REQUEST_URI' );
	$url    = $protocol . "://" . $host . ( $with_port && $server_port ? $port : '' ) . $requri;
	$url    = wpforo_fix_url( $url );

	return esc_url_raw( $url );
}

function wpforo_arr_group_by( $array, $key_by ) {
	if( ! empty( $array ) ) {
		$fltrd = [];
		foreach( $array as $key => $arr ) {
			if( is_numeric( $key ) ) $fltrd[] = $arr[ $key_by ];
		}
		$uniq_arr = array_unique( $fltrd );
		asort( $uniq_arr );

		return $uniq_arr;
	}
}

function wpforo_phrase( $key, $echo = true, $format = 'first-upper' ) {
	static $cache = [];

	$locale    = WPF()->locale;
	$cache_key = $key . '|' . $locale . '|' . $format;

	// For English locale, we can safely cache (no domain/backtrace issues)
	if ( 'en_US' === $locale && isset( $cache[ $cache_key ] ) ) {
		if ( $echo ) {
			echo $cache[ $cache_key ];
		}
		return $cache[ $cache_key ];
	}

	// Normalize key once for phrase lookup
	$normalized_key = addslashes( strtolower( (string) $key ) );
	$phrase         = isset( WPF()->phrase->phrases[ $normalized_key ] ) ? WPF()->phrase->phrases[ $normalized_key ] : $key;

	if( 'en_US' !== $locale ) {
		$native      = $phrase;
		$backtrace   = wp_debug_backtrace_summary();
		$mopo_domain = 'wpforo';
		if( strpos( (string) $backtrace, '\plugins\wpforo-private-messages\\' ) !== false || strpos( (string) $backtrace, '/plugins/wpforo-private-messages/' ) !== false ) $mopo_domain = 'wpforo_pm';
		if( strpos( (string) $backtrace, 'wpForoPolls' ) !== false ) $mopo_domain = 'wpforo_poll';
		$key    = preg_replace( "/(^\s+)|(\s+$)/u", "", (string) $key );
		$phrase = preg_replace( "/(^\s+)|(\s+$)/u", "", (string) $phrase );
		if( strtolower( (string) $key ) === strtolower( (string) $phrase ) ) {
			$phrase = __( $key, $mopo_domain );
			if( strtolower( (string) $key ) === strtolower( (string) $phrase ) ) {
				$key    = strtolower( (string) $key );
				$phrase = __( $key, $mopo_domain );
				if( strtolower( (string) $key ) === strtolower( (string) $phrase ) ) {
					$phrase = __( ucfirst( (string) $key ), $mopo_domain );
					if( strtolower( (string) $key ) === strtolower( (string) $phrase ) ) {
						$phrase = __( $native, $mopo_domain ); //Try all, if no result pass the original text to translation again.
					}
				}
			}
		}
	}

	if( $format === 'first-upper' ) {
		if( 'en_US' !== $locale && function_exists( 'mb_strlen' ) && mb_strlen( (string) $phrase ) !== strlen( (string) $phrase ) && function_exists( 'mb_strtoupper' ) ) {
			$phrase = mb_strtoupper( mb_substr( (string) $phrase, 0, 1 ) ) . mb_substr( (string) $phrase, 1 );
		} else {
			$phrase = ucfirst( (string) $phrase );
		}
	} elseif( $format === 'upper' ) {
		if( function_exists( 'mb_strtoupper' ) ) {
			$phrase = mb_strtoupper( (string) $phrase );
		} else {
			$phrase = strtoupper( (string) $phrase );
		}
	} elseif( $format === 'lower' ) {
		if( function_exists( 'mb_strtolower' ) ) {
			$phrase = mb_strtolower( (string) $phrase );
		} else {
			$phrase = strtolower( (string) $phrase );
		}
	}

	$phrase = str_replace( '{number}', '', $phrase );

	// Cache for English locale (safe - no domain dependency)
	if ( 'en_US' === $locale ) {
		$cache[ $cache_key ] = $phrase;
	}

	if( $echo ) echo $phrase;

	return $phrase;
}

function wpforo_screen_option() { ?>
    <div id="screen-meta" class="metabox-prefs" style="display: none; ">
        <div id="screen-options-wrap" class="hidden" tabindex="-1" aria-label="Screen Options Tab" style="display: none; ">
            <form id="adv-settings" action="" method="POST">
                <input type="hidden" name="wpfaction" value="dashboard_options_save">
                <h5><?php _e( 'Show on screen', 'wpforo' ) ?></h5>

                <div class="screen-options">
                    <input type="number" step="1" min="1" max="999" class="screen-per-page" name="wpforo_dashboard_count_per_page" id="edit_post_per_page" maxlength="3"
                           value="<?php echo wpforo_get_option( 'count_per_page', 10 ) ?>">
                    <label for="edit_post_per_page"><?php _e( 'Items', 'wpforo' ) ?></label>
                    <input type="submit" id="screen-options-apply" class="button" value="<?php _e( 'Apply', 'wpforo' ) ?>">
                </div>
            </form>
        </div>
    </div>

    <div id="screen-meta-links">
        <div id="screen-options-link-wrap" class="hide-if-no-js screen-meta-toggle" style="">
            <a href="#screen-options-wrap" id="show-settings-link" class="show-settings screen-meta-active" aria-controls="screen-options-wrap" aria-expanded="true">
				<?php _e( 'Screen Options', 'wpforo' ) ?>
            </a>
        </div>
    </div>

	<?php
}

function wpforo_strip_shortcodes( $text, $only_wpforo_shortcodes = false ) {
	$text = (string) apply_filters( 'wpforo_strip_shortcodes', (string) $text, $only_wpforo_shortcodes );
	$text = preg_replace( '#\[attach[^\[\]]*][^\[\]]*\[/attach]#iu', '', $text );
	$text = preg_replace( '#\[spoiler[^\[\]]*][^\[\]]*\[/spoiler]#iu', '', $text );
	$text = preg_replace( '#\[quote[^\[\]]*][^\[\]]*\[/quote]#iu', '', $text );
	$text = preg_replace( '#\[wpfgiphy[^\[\]]+?]#iu', '', $text );
	$text = preg_replace( '#\[wpftenor[^\[\]]+?]#iu', '', $text );
	//	$text = preg_replace('#(?:{\w+})?:[^/.\s]+:#iu',                     '', $text);
	if( ! $only_wpforo_shortcodes ) $text = strip_shortcodes( $text );

	return $text;
}

function wpforo_strip_quotes( $text ) {
	$text = preg_replace( '#<(blockquote)[^<>]*?data-userid[^<>]*?>(?:.*?(?R)*.*?)*?</\1>#isu', '', (string) $text );

	return preg_replace( '#\[(quote)[^\[\]]*?data-userid[^\[\]]*?](?:.*?(?R)*.*?)*?\[/\1]#isu', '', (string) $text );
}

function wpforo_strip_urls( $text ) {
	return preg_replace( '#(?:[^\'\"]|^)(https?://[^\s\'\"<>]+)(?:[^\'\"]|$)#iu', '', (string) $text );
}

function wpforo_text( $text, $length = 0, $echo = true, $strip_tags = true, $strip_urls = true, $strip_shortcodes = true, $strip_quotes = true ) {
	$text = (string) $text;
	$text = str_replace( '</p>', '</p> ', $text );
	$text = str_replace( '</div>', '</div> ', $text );

	if( $strip_quotes ) $text = wpforo_strip_quotes( $text );
	if( $strip_urls ) $text = wpforo_strip_urls( $text );
	if( $strip_tags ) $text = strip_tags( (string) $text );
	if( $strip_shortcodes ) $text = wpforo_strip_shortcodes( $text );

	$text = apply_filters( 'wpforo_text', $text, $length, $echo, $strip_tags, $strip_urls, $strip_shortcodes, $strip_quotes );

	$text = trim( str_replace( "\xc2\xa0", ' ', (string) $text ) );

	if( ! $length ) {
		$text = trim( (string) $text );
	} elseif( function_exists( 'mb_substr' ) ) {
		$text = trim(
			mb_substr( (string) $text, 0, $length, get_option( 'blog_charset' ) ) . ( ( function_exists( 'mb_strlen' ) ? mb_strlen( (string) $text, get_option( 'blog_charset' ) ) : strlen(
				(string) $text
			) ) > $length ? '...' : '' )
		);
	} else {
		$text = trim( substr( (string) $text, 0, $length ) . ( strlen( (string) $text ) > $length ? '...' : '' ) );
	}

	if( $echo ) echo $text;

	return $text;
}

function wpforo_admin_options_tabs( $tabs, $current = 'general', $subtab = false, $sub_current = 'general' ) {
	if( ! empty( $tabs ) ) {
		$class_attr = $subtab ? 'vert_tab' : '';
		echo '<h2 class="nav-tab-wrapper ' . esc_attr( $class_attr ) . '">';
		foreach( $tabs as $tab => $name ) {
			$class   = ( $tab === $current || ( $subtab && $tab === $sub_current ) ) ? ' nav-tab-active' : '';
			$sub     = $subtab ? '&subtab=' . esc_attr( $tab ) : '';
			$class   = esc_attr( $class );
			$current = esc_attr( $current );
			$tab     = esc_attr( $tab );
			$sub     = esc_attr( $sub );
			echo "<a class='nav-tab $class $class_attr' href='?page=" . (string) wpfval( $_GET, 'page' ) . "&tab=" . ( $subtab ? $current : $tab ) . "$sub'>$name</a>";
		}
		echo '</h2>';
	}
}

function wpforo_admin_tools_tabs( $tabs, $current = 'antispam', $subtab = false, $sub_current = 'antispam' ) {
	if( ! empty( $tabs ) ) {
		$class_attr = $subtab ? 'vert_tab' : '';
		echo '<h2 class="nav-tab-wrapper ' . esc_attr( $class_attr ) . '">';
		foreach( $tabs as $tab => $name ) {
			$class   = ( $tab == $current || ( $subtab && $tab == $sub_current ) ) ? ' nav-tab-active' : '';
			$sub     = $subtab ? '&subtab=' . esc_attr( $tab ) : '';
			$class   = esc_attr( $class );
			$current = esc_attr( $current );
			$tab     = esc_attr( $tab );
			$sub     = esc_attr( $sub );
			$name    = esc_html( $name );
			echo "<a class='nav-tab$class $class_attr' href='?page=" . wpforo_prefix_slug( 'tools' ) . "&tab=" . ( $subtab ? $current : $tab ) . "$sub' style='float:left'>$name</a>";
		}
		echo '</h2>';
	}
}

function wpforo_content_filter( $content, $post = [] ) {
	$content = (string) $content;
	if( strpos( (string) $content, '../' ) !== false ) {
		$home_url = trim( preg_replace( [ '#/?\?.*$#isu', '#index\.php/?#isu' ], '', home_url() ), '/\\' ) . '/';
		$content  = preg_replace( '#((?:href|src)=[\'\"])(?:https?://)?(?:\.+/)+wp-content/#i', "$1" . $home_url . "wp-content/", (string) $content );
	}
	$content = apply_filters( 'wpforo_body_text_filter', $content, $post );
	if( apply_filters( 'wpforo_auto_embed_image', true, $post ) ) {
		$content = preg_replace(
			'#([^\'\"]|^)(https?://[^\s\'\"<>]+\.(?:jpg|jpeg|png|webp|gif|svg|bmp|tiff))([^\'\"]|$)#iu',
			'$1 <a class="wpforo-auto-embeded-link" href="$2" target="_blank"><img class="wpforo-auto-embeded-image" src="$2"/></a> $3',
			(string) $content
		);
	}
	if( apply_filters( 'wpforo_auto_embed_link', true, $post ) ) {
		$content = preg_replace(
			'#([^\'\"]|^)(https?://[^\s\'\"<>\[\]]+)([^\'\"]|$)#iu',
			'$1 <a class="wpforo-auto-embeded-link" href="$2" target="_blank">$2</a> $3',
			(string) $content
		);
	}
	if( preg_match_all( '#<pre([^<>]*)>(.*?class=[\'"]wpforo-auto-embeded[^\'"]*[\'"].*?)</pre>#isu', (string) $content, $matches, PREG_SET_ORDER ) ) {
		foreach( $matches as $match ) {
			$match[2] = preg_replace( '#<img[^<>]*class=[\'"]wpforo-auto-embeded-image[\'"][^<>]*src=[\'"]([^\'"]*)[\'"][^<>]*>#isu', '$1', $match[2] );
			$match[2] = preg_replace( '#<a[^<>]*class=[\'"]wpforo-auto-embeded-link[\'"][^<>]*href=[\'"]([^\'"]*)[\'"][^<>]*>.*?</a>#isu', '$1', $match[2] );
			$content  = str_replace( $match[0], '<pre' . $match[1] . '>' . $match[2] . '</pre>', $content );
		}
	}
	$content = preg_replace( '#(<a[^<>]*>[^<>]*)<a[^<>]*class=[\'"]wpforo-auto-embeded-link[\'"][^<>]*href=[\'"]([^\'"]*)[\'"][^<>]*>[^<>]*</a>([^<>]*</a>)#iu', '$1$2$3', (string) $content );
	$content = apply_filters( 'wpforo_content_filter', $content, $post );

	return wpautop( $content );
}

function wpforo_remove_links( $content ) {
	return preg_replace( '#([^\'\"]|^)(https?://[^\s\'\"<>]+)([^\'\"]|$)#isu', '$1 [' . wpforo_phrase( 'removed link', false ) . '] $3', (string) $content );
}

add_filter( 'wpforo_content_filter', 'wpforo_nofollow_tag', 20 );
function wpforo_nofollow_tag( $content ) {
	return preg_replace_callback( '#<a[^><]*?href=[\'\"]([^\'\"]+)[\'\"][^><]*?>#isu', 'wpforo_nofollow', (string) $content );
}

function wpforo_nofollow( $match ) {
	$ret = $match[0];
	if( apply_filters( 'wpforo_external_link_nofollow', true ) ) {
		$dofollow          = wpforo_setting( 'seo', 'dofollow' );
		$request_uri_parse = parse_url( wpforo_get_request_uri() );
		$main_host         = preg_replace( '#^.*?([^.]+?\.[^.]+?)$#isu', '$1', (string) $request_uri_parse['host'] );
		$link_url          = parse_url( $match[1] );
		if( strpos( (string) $match[1], $main_host ) === false && ! ( ! empty( $dofollow ) && ! empty( $link_url['host'] ) && in_array( $link_url['host'], $dofollow ) ) ) {
			$ret = preg_replace_callback(
				'#\srel=[\'\"]([^\'\"]*?)[\'\"]#iu',
				function( $m ) {
					$rels = array_filter( preg_split( '#\s#u', $m[1] ) );
					$rels = array_merge( [ 'nofollow' ], $rels );

					return sprintf( 'rel="%1$s"', implode( ' ', $rels ) );
				},
				(string) $match[0],
				1,
				$count
			);
			if( ! $count ) $ret = str_replace( '>', ' rel="nofollow">', $match[0] );
		}
	}

	return $ret;
}

add_action( 'wpforo_actions_end', 'wpforo_logs', 10 );
function wpforo_logs() {
	if( ! WPF()->current_object['is_404'] ) {
		WPF()->log->read();
		WPF()->log->visit();
	}
}

add_action( 'wpforo_bottom_hook', 'wpforo_user_logging' );
function wpforo_user_logging() {
	$data         = WPF()->current_object;
	$filter_views = apply_filters( 'wpforo_filter_topic_views', true );
	if( wpfval( $data, 'template' ) && $data['template'] == 'post' && wpfval( $data, 'topicid' ) ) {
		if( $filter_views ) {
			//to-do: don't increase views before all read point.
			if( wpforo_setting( 'legal', 'cookies' ) ) {
				$viwed_ids = wpforo_getcookie( wpforo_prefix( 'read_topics' ), false );
				$has_viewed = ! empty( $viwed_ids ) && wpfval( $viwed_ids, $data['topicid'] );
				if( ! $has_viewed && is_user_logged_in() ) {
					$viwed_db_ids = wpforo_current_usermeta( wpforo_prefix( 'read_topics' ) );
					$has_viewed = ! empty( $viwed_db_ids ) && wpfval( $viwed_db_ids, $data['topicid'] );
				}
				if( ! $has_viewed ) {
					WPF()->db->query( "UPDATE `" . WPF()->tables->topics . "` SET `views` = `views` + 1 WHERE `topicid` = " . intval( $data['topicid'] ) );
				}
			} elseif( is_user_logged_in() ) {
				if( wpfval( WPF()->current_usermeta, wpforo_prefix( 'read_topics' ) ) ) {
					$viwed_db_ids = wpforo_current_usermeta( wpforo_prefix( 'read_topics' ) );
					if( empty( $viwed_db_ids ) || ! wpfval( $viwed_db_ids, $data['topicid'] ) ) {
						WPF()->db->query( "UPDATE `" . WPF()->tables->topics . "` SET `views` = `views` + 1 WHERE `topicid` = " . intval( $data['topicid'] ) );
					}
				} else {
					WPF()->db->query( "UPDATE `" . WPF()->tables->topics . "` SET `views` = `views` + 1 WHERE `topicid` = " . intval( $data['topicid'] ) );
				}
			}
		} else {
			WPF()->db->query( "UPDATE `" . WPF()->tables->topics . "` SET `views` = `views` + 1 WHERE `topicid` = " . intval( $data['topicid'] ) );
		}
	}
}

function wpforo_setcookie( $key, $args = [], $implode = false ) {
	if( ! wpforo_setting( 'legal', 'cookies' ) ) return;
	if( ! empty( $args ) && is_array( $args ) ) {
		$num = count( $args );
		if( $num > 3 ) {
			$max   = apply_filters( 'wpforo_cookie_max_logged_topics', 10 );
			$delta = $num - $max;
			if( $delta > 0 ) $args = array_slice( $args, $delta, null, true );
		}
	}
	if( ! empty( $args ) && is_array( $args ) && $implode ) {
		$value = trim( implode( ',', $args ), ',' );
	} elseif( ! empty( $args ) && is_array( $args ) && ! $implode ) {
		$value = wp_json_encode( $args );
	}
	if( ! isset( $value ) ) $value = '';
	if( $key ) {
		$secure                  = is_ssl();
		$secure_logged_in_cookie = $secure && 'https' === parse_url( get_option( 'home' ), PHP_URL_SCHEME );
		if( COOKIEPATH != SITECOOKIEPATH ) {
			@setcookie( $key, $value, time() + 7776000, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true );
		} else {
			@setcookie( $key, $value, time() + 7776000, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true );
		}
	}
}

function wpforo_getcookie( $key, $explode = false ) {
	if( ! wpforo_setting( 'legal', 'cookies' ) ) return false;
	if( $cookie = wpfval( $_COOKIE, $key ) ) {
		if( $explode ) {
			return explode( ',', $cookie );
		} else {
			$cookie = wp_unslash( $cookie );
			if( ! $data = json_decode( $cookie, true ) ) return $cookie;

			return $data;
		}
	}

	return false;
}

function wpforo_is_bot() {
	static $is_bot = null;

	if ( $is_bot !== null ) {
		return $is_bot;
	}

	if( ! $http_user_agent = wpfval( $_SERVER, 'HTTP_USER_AGENT' ) ) $http_user_agent = wpfval( $_SERVER, 'http_user_agent' );
	if( $http_user_agent ) {
		$is_bot = (bool) preg_match(
			'#(abot|dbot|ebot|hbot|kbot|lbot|mbot|nbot|obot|pbot|rbot|sbot|tbot|vbot|ybot|zbot|bot\.|bot\/|_bot|\.bot|\/bot|\-bot|\:bot|\(bot|crawl|slurp|spider|seek|accoona|acoon|adressendeutschland|ah\-ha\.com|ahoy|altavista|ananzi|anthill|appie|arachnophilia|arale|araneo|aranha|architext|aretha|arks|asterias|atlocal|atn|atomz|augurfind|backrub|bannana_bot|baypup|bdfetch|big brother|biglotron|bjaaland|blackwidow|blaiz|blog|blo\.|bloodhound|boitho|booch|bradley|butterfly|calif|cassandra|ccubee|cfetch|charlotte|churl|cienciaficcion|cmc|collective|comagent|combine|computingsite|csci|curl|cusco|daumoa|deepindex|delorie|depspid|deweb|die blinde kuh|digger|ditto|dmoz|docomo|download express|dtaagent|dwcp|ebiness|ebingbong|e\-collector|ejupiter|emacs\-w3 search engine|esther|evliya celebi|ezresult|falcon|felix ide|ferret|fetchrover|fido|findlinks|fireball|fish search|fouineur|funnelweb|gazz|gcreep|genieknows|getterroboplus|geturl|glx|goforit|golem|grabber|grapnel|gralon|griffon|gromit|grub|gulliver|hamahakki|harvest|havindex|helix|heritrix|hku www octopus|homerweb|htdig|html index|html_analyzer|htmlgobble|hubater|hyper\-decontextualizer|ia_archiver|ibm_planetwide|ichiro|iconsurf|iltrovatore|image\.kapsi\.net|imagelock|incywincy|indexer|infobee|informant|ingrid|inktomisearch\.com|inspector web|intelliagent|internet shinchakubin|ip3000|iron33|israeli\-search|ivia|jack|jakarta|javabee|jetbot|jumpstation|katipo|kdd\-explorer|kilroy|knowledge|kototoi|kretrieve|labelgrabber|lachesis|larbin|legs|libwww|linkalarm|link validator|linkscan|lockon|lwp|lycos|magpie|mantraagent|mapoftheinternet|marvin\/|mattie|mediafox|mediapartners|mercator|merzscope|microsoft url control|minirank|miva|mj12|mnogosearch|moget|monster|moose|motor|multitext|muncher|muscatferret|mwd\.search|myweb|najdi|nameprotect|nationaldirectory|nazilla|ncsa beta|nec\-meshexplorer|nederland\.zoek|netcarta webmap engine|netmechanic|netresearchserver|netscoop|newscan\-online|nhse|nokia6682\/|nomad|noyona|nutch|nzexplorer|objectssearch|occam|omni|open text|openfind|openintelligencedata|orb search|osis\-project|pack rat|pageboy|pagebull|page_verifier|panscient|parasite|partnersite|patric|pear\.|pegasus|peregrinator|pgp key agent|phantom|phpdig|picosearch|piltdownman|pimptrain|pinpoint|pioneer|piranha|plumtreewebaccessor|pogodak|poirot|pompos|poppelsdorf|poppi|popular iconoclast|psycheclone|publisher|python|rambler|raven search|roach|road runner|roadhouse|robbie|robofox|robozilla|rules|salty|sbider|scooter|scoutjet|scrubby|search\.|searchprocess|semanticdiscovery|senrigan|sg\-scout|shai\'hulud|shark|shopwiki|sidewinder|sift|silk|simmany|site searcher|site valet|sitetech\-rover|skymob\.com|sleek|smartwit|sna\-|snappy|snooper|sohu|speedfind|sphere|sphider|spinner|spyder|steeler\/|suke|suntek|supersnooper|surfnomore|sven|sygol|szukacz|tach black widow|tarantula|templeton|\/teoma|t\-h\-u\-n\-d\-e\-r\-s\-t\-o\-n\-e|theophrastus|titan|titin|tkwww|toutatis|t\-rex|tutorgig|twiceler|twisted|ucsd|udmsearch|url check|updated|vagabondo|valkyrie|verticrawl|victoria|vision\-search|volcano|voyager\/|voyager\-hc|w3c_validator|w3m2|w3mir|walker|wallpaper|wanderer|wauuu|wavefire|web core|web hopper|web wombat|webbandit|webcatcher|webcopy|webfoot|weblayers|weblinker|weblog monitor|webmirror|webmonkey|webquest|webreaper|websitepulse|websnarf|webstolperer|webvac|webwalk|webwatch|webwombat|webzinger|wget|whizbang|whowhere|wild ferret|wordpress|worldlight|wwwc|wwwster|xenu|xget|xift|xirq|yandex|yanga|yeti|yodao|zao\/|zippp|zyborg|\.\.\.\.|InspectionTool)#i',
			(string) $http_user_agent
		);

		return $is_bot;
	}

	$is_bot = true;

	return $is_bot;
}

//Option value filter and output
function wpfo( $option = '', $echo = true, $esc = 'esc_attr' ) {
	if( is_string( $option ) ) {
		$option = stripslashes( (string) $option );
		if( $esc === 'esc_attr' ) {
			$option = esc_attr( $option );
		} elseif( $esc === 'esc_html' ) {
			$option = esc_html( $option );
		} elseif( $esc === 'esc_url' ) {
			$option = esc_url( (string) $option );
		} elseif( $esc === 'esc_textarea' ) {
			$option = esc_textarea( (string) $option );
		}
	}

	if( $echo ) {
		echo $option;
	} else {
		return $option;
	}
}

//Option maker for checkbox, radio and select
function wpfo_check( $option = '', $value = '', $type = 'checked', $echo = true ) {
	$option = ( isset( $option ) && isset( $value ) && $option == $value ) ? $type : '';
	if( $echo ) {
		echo $option;
	} else {
		return $option;
	}
}

/**
 * Check if nested array keys exist.
 *
 * Traverses nested arrays to validate if a path of keys exists.
 * All arguments after $array are treated as keys to traverse - this function
 * does NOT support a default value parameter.
 *
 * @example
 * // Check if $arr['user']['profile']['name'] exists:
 * wpfkey( $arr, 'user', 'profile', 'name' )  // Returns true/false
 *
 * // WRONG - this tries to check $arr['key'][0], not use 0 as default:
 * wpfkey( $arr, 'key', 0 )  // Checks for $arr['key'][0], NOT a default!
 *
 * @param array      $array The array to check
 * @param string|int $key   First key to check
 * @param string|int ...$_  Additional keys to traverse (NOT a default value!)
 *
 * @return bool True if all keys exist in the nested path, false otherwise
 *
 * @see wpfval() To get the value at a nested path
 * @see wpffix() To get a value with a default fallback
 */
function wpfkey( &$array, $key ) {
	$a = $array;
	foreach( func_get_args() as $arg_num => $key ) {
		if( $arg_num === 0 ) continue;
		if( is_array( $a ) && ( is_string( $key ) || is_int( $key ) ) && array_key_exists( $key, $a ) ) {
			$a = $a[ $key ];
		} else {
			return false;
		}
	}

	return true;
}

/**
 * Get value from nested array keys, or null if path doesn't exist.
 *
 * Traverses nested arrays to retrieve a value at a given key path.
 * All arguments after $array are treated as keys to traverse - this function
 * does NOT support a default value parameter.
 *
 * IMPORTANT: This function does NOT accept a default value!
 * Use wpffix() or the null coalescing operator for defaults.
 *
 * @example
 * // Get $arr['user']['profile']['name']:
 * wpfval( $arr, 'user', 'profile', 'name' )  // Returns value or null
 *
 * // WRONG - this tries to access $arr['key'][0], not use 0 as default:
 * wpfval( $arr, 'key', 0 )  // Accesses $arr['key'][0], NOT a default!
 *
 * // CORRECT - use ?: or ?? for defaults:
 * wpfval( $arr, 'key' ) ?: 'default'    // Fallback for falsy values
 * wpfval( $arr, 'key' ) ?? 'default'    // Fallback for null only
 *
 * // CORRECT - use wpffix() for defaults:
 * wpffix( 'default', $arr, 'key' )      // Returns 'default' if key missing
 *
 * @param mixed      $array The array to traverse
 * @param string|int ...$_  Keys to traverse (NOT a default value!)
 *
 * @return mixed|null The value at the key path, or null if any key doesn't exist
 *
 * @see wpfkey() To check if a nested path exists
 * @see wpffix() To get a value with a default fallback
 */
function wpfval( $array ) {
	if( func_num_args() === 1 ) return $array;
	$a = $array;
	foreach( func_get_args() as $arg_num => $key ) {
		if( $arg_num === 0 ) continue;
		if( is_array( $a ) && ( is_string( $key ) || is_int( $key ) ) && array_key_exists( $key, $a ) ) {
			$a = $a[ $key ];
		} else {
			return null;
		}
	}

	return $a;
}

/**
 * Always return the preferred default value if the array, or array index is undefined.
 *
 * @param mixed         the default value if current index is undefined in the array
 * @param mixed         the $array to be checked
 * @param string|int    ... $_, ... [optional] more keys
 *
 * @return mixed|null
 */
function wpffix( $return, $array, $a = null, $b = null, $c = null ) {
	$value = wpfval( $array, $a, $b, $c );
	if( ! is_null( $value ) ) {
		return $value;
	}

	return $return;
}

function wpforo_human_filesize( $bytes, $decimals = 2 ) {
	$size   = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
	$factor = floor( ( strlen( (string) $bytes ) - 1 ) / 3 );

	return sprintf( "%.{$decimals}f", $bytes / pow( 1024, $factor ) ) . '&nbsp;' . @$size[ $factor ];
}

function wpforo_human_time_diff( int $from, bool $ago = true, int $to = 0 ): string {
	if( ! $to ) $to = time();
	$d = human_time_diff( $from, $to );
	if( $ago ) $d = sprintf( wpforo_phrase( '%s ago', false, false ), $d );

	return $d;
}

/**
 * @param int | string $date
 *
 * @return int
 */
function wpforo_gmtimestamp( $date = 0 ): int {
	if( ! $date ) $date = time();

	return intval( is_numeric( $date ) ? $date : strtotime( $date . ' GMT' ) );
}

function wpforo_timezone( string $timezone = '' ): array {
	$timezone_string = '';
	$current_offset  = null;
	if( $timezone ) {
		if( $timezone === 'wp' ) {
			$timezone_string = get_option( 'timezone_string', '' );
			$current_offset  = get_option( 'gmt_offset', null );
		} else {
			if( preg_match( '#UTC\s*([-+])\s*([\d.]+)#i', (string) $timezone, $timezone_array ) ) {
				$current_offset = $timezone_array[1] . $timezone_array[2];
			} elseif( in_array( $timezone, timezone_identifiers_list() ) ) {
				$timezone_string = $timezone;
			}
		}
	}

	return compact( 'timezone_string', 'current_offset' );
}

/**
 * @param int | string $date
 * @param string $timezone
 *
 * @return int
 */
function wpforo_timestamp( $date = 0, string $timezone = '' ): int {
	$timestamp = wpforo_gmtimestamp( $date );
	$tmz       = wpforo_timezone( $timezone );

	if( is_numeric( $tmz['current_offset'] ) ) {
		$timestamp += $tmz['current_offset'] * 3600;
	} elseif( $tmz['timezone_string'] && class_exists( 'DateTime' ) && class_exists( 'DateTimeZone' ) ) {
		try {
			$dt        = new DateTime( gmdate( 'Y-m-d H:i:s', $timestamp ), new DateTimeZone( $tmz['timezone_string'] ) );
			$timestamp += $dt->getOffset();
		} catch ( Exception $e ) {
		}
	}

	return (int) $timestamp;
}

/**
 * @param int $gmttime
 * @param string $format
 * @param bool $wp_date_format
 *
 * @return string
 */
function wpforo_date_format( int $gmttime, string $format, bool $wp_date_format = true ): string {
	if( $format === 'mysql' ) {
		return 'Y-m-d H:i:s';
	} elseif( in_array( $format, [ 'human', 'ago', 'timestamp' ], true ) ) {
		return $format;
	}

	if( $format === 'ago-date' ) {
		$diff = time() - $gmttime;
		if( $diff > 31536000 ) {
			return apply_filters( 'wpforo_date_format_ago_date_year', 'M d, y' );
		} elseif( $diff > 518400 ) {
			return apply_filters( 'wpforo_date_format_ago_date_month', 'M d' );
		} else {
			return 'ago';
		}
	}

	$sep = ' ';
	if( $wp_date_format ) {
		$date_format = get_option( 'date_format' );
		$time_format = get_option( 'time_format' );
	} else {
		$date_format = 'Y-m-d';
		$time_format = 'H:i:s';
	}
	if( $format === 'datetime' ) {
		$format = $date_format . $sep . $time_format;
	} elseif( $format === 'date' ) {
		$format = $date_format;
	} elseif( $format === 'time' ) {
		$format = $time_format;
	} elseif( $wp_date_format ) {
		$format = $date_format . $sep . $time_format;
	}

	return $format;
}

/**
 * @param $date string | int
 * @param $format string
 * @param $timezone string
 *
 * @return string | int
 */
function _wpforo_date( $date = 0, string $format = 'Y-m-d H:i:s', string $timezone = '' ) {
	if( in_array( $format, [ 'human', 'ago' ], true ) ) {
		$d = wpforo_human_time_diff( wpforo_gmtimestamp( $date ), $format === 'ago' );
	} else {
		$timestamp = wpforo_timestamp( $date, $timezone );
		if( $format === 'timestamp' ) {
			$d = $timestamp;
		} else {
			if( $format === 'mysql' ) $format = 'Y-m-d H:i:s';
			$d = date_i18n( $format, $timestamp );
		}
	}

	return $d;
}

/**
 * @param int | string $date
 * @param string $format
 * @param bool $echo
 * @param bool $wp_date_format
 * @param string|null $timezone
 *
 * @return string | int
 */
function wpforo_date( $date, $format = 'ago', $echo = true, $wp_date_format = true, $timezone = null ) {
	$wp_date_format = $wp_date_format && wpforo_setting( 'general', 'wp_date_format' );
	$format         = wpforo_date_format( wpforo_gmtimestamp( $date ), $format, $wp_date_format );

	if( is_null( $timezone ) ) $timezone = ( (string) wpfval( WPF()->current_user, 'timezone' ) ?: ( (string) wpfval( $_COOKIE, 'wpforo_browser_timezone' ) ?: 'wp' ) );
	$timezone = apply_filters( 'wpforo_date_timezone', $timezone );

	$d = _wpforo_date( $date, $format, $timezone );
	$d = apply_filters( 'wpforo_date', $d, $date, $format, $timezone );

	if( $echo ) echo $d;

	return $d;
}

if( ! function_exists( 'wpforo_date_raw' ) ) {
	/**
	 * @param string|int $date
	 * @param string $format
	 * @param bool $echo
	 * @param string|null $timezone
	 *
	 * @return string | int
	 */
	function wpforo_date_raw( $date, $format = 'ago', $echo = true, $timezone = null ) {
		return wpforo_date( $date, $format, $echo, false, $timezone );
	}
}

function wpforo_write_file( string $new_file, $content ): array {
	$return   = [ 'error' => '', 'file' => '' ];
	$new_file = wpforo_fix_dir_sep( $new_file );
	$dir      = dirname( $new_file );
	if( ! is_dir( $dir ) ) wp_mkdir_p( $dir );
	$ifp = @fopen( $new_file, 'wb' );
	if( ! $ifp ) {
		if( is_resource( $ifp ) ) {
			@fclose( $ifp );
		}
		$return = [ 'error' => sprintf( __( 'Could not write file %s' ), $new_file ) ];
	} else {
		@fwrite( $ifp, $content );
		fclose( $ifp );
		clearstatcache();
		// Set correct file permissions
		$stat  = @stat( dirname( $new_file ) );
		$perms = $stat['mode'] & 0007777;
		$perms = $perms & 0000666;
		@chmod( $new_file, $perms );
		clearstatcache();
		$return['file'] = $new_file;
	}

	return $return;
}

function wpforo_get_file_content( $file ) {
	if( is_file( $file ) ) {
		$fp = @fopen( $file, 'r' );
		if( ! $fp ) {
			if( is_resource( $fp ) ) {
				@fclose( $fp );
			}
		} else {
			$size = @filesize( $file );
			if( $size > 0 ) {
				$file_data = fread( $fp, $size );
				fclose( $fp );

				return $file_data;
			}
			@fclose( $fp );
		}
	}

	return false;
}

################################################################################
/**
 * Clears file basename and removes trailing slash
 *
 * @param string        filename
 *
 * @return    string
 * @since 1.0.0
 *
 */
function wpforo_clear_basename( $file ) {
	$file = (string) $file;
	$file = str_replace( '\\', '/', $file );
	$file = preg_replace( '|/+|', '/', $file );
	$file = trim( (string) $file, '/' );

	return $file;
}

#################################################################################
/**
 * Removes directory with all files and folders
 *
 * @param string        directory name
 *
 * @since 1.0.0
 *
 */
function _wpforo_remove_directory( $directory ) {
	$directory    = (string) $directory;
	$directory    = wpforo_fix_dir_sep( $directory );
	$directory_ns = trim( $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
	$directory_ws = DIRECTORY_SEPARATOR . trim( $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
	$glob         = glob( $directory_ns . '*' );
	if( empty( $glob ) ) $glob = glob( $directory_ws . '*' );
	foreach( $glob as $item ) {
		if( is_dir( $item ) ) {
			_wpforo_remove_directory( $item );
		} else {
			unlink( $item );
		}
	}

	return rmdir( $directory );
}

#################################################################################
/**
 * Removes directory with all files and folders
 *
 * @param string        directory name
 *
 * @since 1.0.0
 *
 */
function wpforo_remove_directory( $file, $recursive = true ) {
	if( ! class_exists( 'WP_Filesystem_Direct' ) ) {
		require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
		require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
	}
	$file                 = wpforo_fix_dir_sep( $file );
	$WP_Filesystem_Direct = new WP_Filesystem_Direct( null );

	return $WP_Filesystem_Direct->delete( $file, $recursive );
}

#################################################################################
/**
 * Converts bytes to KB, MB, GB
 *
 * @param integer        Bytes
 *
 * @return    string
 * @since 1.0.0
 *
 */
function wpforo_print_size( $value, $points = true ) {
	if( $value < 1024 ) {
		return $value . ( ( $points ) ? "B" : '' );
	} elseif( $value >= 1024 && $value < ( 1024 * 1024 ) ) {
		$value = round( ( $value / 1024 ) * 10 ) / 10;

		return $value . ( ( $points ) ? "KB" : '' );
	} elseif( $value >= 1024 * 1024 && $value < 1024 * 1024 * 1024 ) {
		$value = round( ( $value / ( 1024 * 1024 ) ) * 10 ) / 10;

		return $value . ( ( $points ) ? "MB" : '' );
	} elseif( $value >= 1024 * 1024 * 1024 && $value <= 1024 * 1024 * 1024 * 1024 ) {
		$value = round( ( $value / ( 1024 * 1024 * 1024 ) ) * 10 ) / 10;

		return $value . ( ( $points ) ? "GB" : '' );
	} else {
		$value = round( ( $value / ( 1024 * 1024 * 1024 * 1024 ) ) * 10 ) / 10;

		return $value . ( ( $points ) ? "TB" : '' );
	}
}

function wpforo_human_size_to_bytes( $sSize ) {
	if( is_numeric( $sSize ) ) return $sSize;

	$sSuffix = substr( (string) $sSize, - 1 );
	$iValue  = substr( (string) $sSize, 0, - 1 );
	switch( strtoupper( (string) $sSuffix ) ) {
		case 'M':
			$iValue *= 1024 * 1024;
		break;
		case 'K':
			$iValue *= 1024;
		break;
		case 'G':
			$iValue *= 1024 * 1024 * 1024;
		break;
		case 'T':
			$iValue *= 1024 * 1024 * 1024 * 1024;
		break;
		case 'P':
			$iValue *= 1024 * 1024 * 1024 * 1024 * 1024;
		break;
	}

	return $iValue;
}

function wpforo_print_number( $n, $echo = false ) {
	$x      = str_replace( ",", "", $n );
	$x      = intval( $x );
	$n      = 0 + $x;
	$number = 0;
	if( ! is_numeric( $n ) ) return false;
	if( $n > 1000000000000 ) {
		$number = round( ( $n / 1000000000000 ), 1 ) . ' ' . str_replace( '{number}', '', wpforo_phrase( '{number}T', false ) );
	} else if( $n > 1000000000 ) {
		$number = round( ( $n / 1000000000 ), 1 ) . ' ' . str_replace( '{number}', '', wpforo_phrase( '{number}B', false ) );
	} else if( $n > 1000000 ) {
		$number = round( ( $n / 1000000 ), 1 ) . ' ' . str_replace( '{number}', '', wpforo_phrase( '{number}M', false ) );
	} else if( $n > 10000 ) $number = round( ( $n / 1000 ), 1 ) . ' ' . str_replace( '{number}', '', wpforo_phrase( '{number}K', false ) );

	$number = ( $number ) ? $number : number_format( $n );

	if( $echo ) {
		echo $number;
	} else {
		return $number;
	}
}

function wpforo_bigintval( $value ) {
	$value = wpforo_settype( $value, 'string' );
	if( is_string( $value ) ) {
		$value = trim( $value );
		if( ! is_numeric( $value ) ) {
			$value = preg_replace( "#[^0-9].*#s", '', (string) $value );
			if( ! is_numeric( $value ) ) $value = 0;
		}
	} else {
		$value = 0;
	}

	return ( strlen( (string) $value ) < strlen( (string) PHP_INT_MAX ) ) ? (int) $value : $value;
}

function wpforo_removebb( $string ) {
	if( isset( $string ) && $string ) {
		$string = preg_replace( '|\[/*[^]\[]+]|i', '', (string) $string );
	}

	return $string;
}

function wpforo_file_upload_error( $code ): string {
	switch( $code ) {
		case UPLOAD_ERR_INI_SIZE:
			$message = wpforo_phrase( "The uploaded file exceeds the upload_max_filesize directive in php.ini", false );
		break;
		case UPLOAD_ERR_FORM_SIZE:
			$message = wpforo_phrase( "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form", false );
		break;
		case UPLOAD_ERR_PARTIAL:
			$message = wpforo_phrase( "The uploaded file was only partially uploaded", false );
		break;
		case UPLOAD_ERR_NO_FILE:
			$message = wpforo_phrase( "No file was uploaded", false );
		break;
		case UPLOAD_ERR_NO_TMP_DIR:
			$message = wpforo_phrase( "Missing a temporary folder", false );
		break;
		case UPLOAD_ERR_CANT_WRITE:
			$message = wpforo_phrase( "Failed to write file to disk", false );
		break;
		case UPLOAD_ERR_EXTENSION:
			$message = wpforo_phrase( "File upload stopped by extension", false );
		break;
		default:
			$message = wpforo_phrase( "Unknown upload error", false ) . ": " . $code;
		break;
	}

	return $message;
}

//$key allowed values are post, strip, data, user_description entities or the name of a field filter such as pre_user_description.
//More info https://core.trac.wordpress.org/browser/tags/4.5.2/src/wp-includes/kses.php#L624
function wpforo_kses( $string = '', $key = 'post' ) {
	if( ! $string || ! $key ) return $string;
	if( $key === 'email' ) {
		if( ! preg_match( '#<p[^<>]*?>#iu', (string) $string ) ) $string = wpautop( $string );
		$allowed_html = [
			'a'          => [ 'href' => true, 'title' => true, 'style' => true ],
			'img'        => [
				'src'    => true,
				'width'  => true,
				'height' => true,
				'align'  => true,
				'alt'    => true,
				'style'  => true,
			],
			'blockquote' => [ 'style' => true ],
			'h1'         => [ 'style' => true ],
			'h2'         => [ 'style' => true ],
			'h3'         => [ 'style' => true ],
			'h4'         => [ 'style' => true ],
			'h5'         => [ 'style' => true ],
			'h6'         => [ 'style' => true ],
			'hr'         => [],
			'br'         => [],
			'p'          => [ 'style' => true ],
			'strong'     => [ 'style' => true ],
			'em'         => [ 'style' => true ],
			'del'        => [ 'style' => true ],
			'style'      => [],
		];
		$allowed_html = apply_filters( 'wpforo_kses_allowed_html_email', $allowed_html );
	} elseif( $key === 'user_description' ) {
		$allowed_html        = wp_kses_allowed_html( $key );
		$allowed_html['img'] = [
			'alt'      => true,
			'align'    => true,
			'border'   => true,
			'height'   => true,
			'hspace'   => true,
			'longdesc' => true,
			'vspace'   => true,
			'src'      => true,
			'usemap'   => true,
			'width'    => true,
		];
		$allowed_html['br']  = [];
		$allowed_html        = apply_filters( 'wpforo_kses_allowed_html_user_description', $allowed_html );
	} elseif( $key === 'svg' ) {
		$allowed_tags  = [
			// HTML
			'a',
			'font',
			'image',
			'style',

			// SVG
			'svg',
			'altglyph',
			'altglyphdef',
			'altglyphitem',
			'animatecolor',
			'animatemotion',
			'animatetransform',
			'circle',
			'clippath',
			'defs',
			'desc',
			'ellipse',
			'filter',
			'font',
			'g',
			'glyph',
			'glyphref',
			'hkern',
			'image',
			'line',
			'lineargradient',
			'marker',
			'mask',
			'metadata',
			'mpath',
			'path',
			'pattern',
			'polygon',
			'polyline',
			'radialgradient',
			'rect',
			'stop',
			'switch',
			'symbol',
			'text',
			'textpath',
			'title',
			'tref',
			'tspan',
			'use',
			'view',
			'vkern',

			// SVG Filters
			'feBlend',
			'feColorMatrix',
			'feComponentTransfer',
			'feComposite',
			'feConvolveMatrix',
			'feDiffuseLighting',
			'feDisplacementMap',
			'feDistantLight',
			'feFlood',
			'feFuncA',
			'feFuncB',
			'feFuncG',
			'feFuncR',
			'feGaussianBlur',
			'feMerge',
			'feMergeNode',
			'feMorphology',
			'feOffset',
			'fePointLight',
			'feSpecularLighting',
			'feSpotLight',
			'feTile',
			'feTurbulence',

			//text
			'#text',
		];
		$allowed_attrs = [
			// HTML
			'about',
			'accept',
			'action',
			'align',
			'alt',
			'autocomplete',
			'background',
			'bgcolor',
			'border',
			'cellpadding',
			'cellspacing',
			'checked',
			'cite',
			'class',
			'clear',
			'color',
			'cols',
			'colspan',
			'coords',
			'crossorigin',
			'datetime',
			'default',
			'dir',
			'disabled',
			'download',
			'enctype',
			'encoding',
			'face',
			'for',
			'headers',
			'height',
			'hidden',
			'high',
			'href',
			'hreflang',
			'id',
			'integrity',
			'ismap',
			'label',
			'lang',
			'list',
			'loop',
			'low',
			'max',
			'maxlength',
			'media',
			'method',
			'min',
			'multiple',
			'name',
			'noshade',
			'novalidate',
			'nowrap',
			'open',
			'optimum',
			'pattern',
			'placeholder',
			'poster',
			'preload',
			'pubdate',
			'radiogroup',
			'readonly',
			'rel',
			'required',
			'rev',
			'reversed',
			'role',
			'rows',
			'rowspan',
			'spellcheck',
			'scope',
			'selected',
			'shape',
			'size',
			'sizes',
			'span',
			'srclang',
			'start',
			'src',
			'srcset',
			'step',
			'style',
			'summary',
			'tabindex',
			'title',
			'type',
			'usemap',
			'valign',
			'value',
			'version',
			'width',
			'xmlns',

			// SVG
			'accent-height',
			'accumulate',
			'additivive',
			'alignment-baseline',
			'ascent',
			'attributename',
			'attributetype',
			'azimuth',
			'basefrequency',
			'baseline-shift',
			'begin',
			'bias',
			'by',
			'class',
			'clip',
			'clip-path',
			'clip-rule',
			'color',
			'color-interpolation',
			'color-interpolation-filters',
			'color-profile',
			'color-rendering',
			'cx',
			'cy',
			'd',
			'dx',
			'dy',
			'diffuseconstant',
			'direction',
			'display',
			'divisor',
			'dur',
			'edgemode',
			'elevation',
			'end',
			'fill',
			'fill-opacity',
			'fill-rule',
			'filter',
			'filterUnits',
			'flood-color',
			'flood-opacity',
			'font-family',
			'font-size',
			'font-size-adjust',
			'font-stretch',
			'font-style',
			'font-variant',
			'font-weight',
			'fx',
			'fy',
			'g1',
			'g2',
			'glyph-name',
			'glyphref',
			'gradientunits',
			'gradienttransform',
			'height',
			'href',
			'id',
			'image-rendering',
			'in',
			'in2',
			'k',
			'k1',
			'k2',
			'k3',
			'k4',
			'kerning',
			'keypoints',
			'keysplines',
			'keytimes',
			'lang',
			'lengthadjust',
			'letter-spacing',
			'kernelmatrix',
			'kernelunitlength',
			'lighting-color',
			'local',
			'marker-end',
			'marker-mid',
			'marker-start',
			'markerheight',
			'markerunits',
			'markerwidth',
			'maskcontentunits',
			'maskunits',
			'max',
			'mask',
			'media',
			'method',
			'mode',
			'min',
			'name',
			'numoctaves',
			'offset',
			'operator',
			'opacity',
			'order',
			'orient',
			'orientation',
			'origin',
			'overflow',
			'paint-order',
			'path',
			'pathlength',
			'patterncontentunits',
			'patterntransform',
			'patternunits',
			'points',
			'preservealpha',
			'preserveaspectratio',
			'r',
			'rx',
			'ry',
			'radius',
			'refx',
			'refy',
			'repeatcount',
			'repeatdur',
			'restart',
			'result',
			'rotate',
			'scale',
			'seed',
			'shape-rendering',
			'specularconstant',
			'specularexponent',
			'spreadmethod',
			'stddeviation',
			'stitchtiles',
			'stop-color',
			'stop-opacity',
			'stroke-dasharray',
			'stroke-dashoffset',
			'stroke-linecap',
			'stroke-linejoin',
			'stroke-miterlimit',
			'stroke-opacity',
			'stroke',
			'stroke-width',
			'style',
			'surfacescale',
			'tabindex',
			'targetx',
			'targety',
			'transform',
			'text-anchor',
			'text-decoration',
			'text-rendering',
			'textlength',
			'type',
			'u1',
			'u2',
			'unicode',
			'values',
			'viewbox',
			'visibility',
			'vector-effect',
			'vert-adv-y',
			'vert-origin-x',
			'vert-origin-y',
			'width',
			'word-spacing',
			'wrap',
			'writing-mode',
			'xchannelselector',
			'ychannelselector',
			'x',
			'x1',
			'x2',
			'xmlns',
			'y',
			'y1',
			'y2',
			'z',
			'zoomandpan',

			// MathML
			'accent',
			'accentunder',
			'align',
			'bevelled',
			'close',
			'columnsalign',
			'columnlines',
			'columnspan',
			'denomalign',
			'depth',
			'dir',
			'display',
			'displaystyle',
			'fence',
			'frame',
			'height',
			//			'href',
			'id',
			'largeop',
			'length',
			'linethickness',
			'lspace',
			'lquote',
			'mathbackground',
			'mathcolor',
			'mathsize',
			'mathvariant',
			'maxsize',
			'minsize',
			'movablelimits',
			'notation',
			'numalign',
			'open',
			'rowalign',
			'rowlines',
			'rowspacing',
			'rowspan',
			'rspace',
			'rquote',
			'scriptlevel',
			'scriptminsize',
			'scriptsizemultiplier',
			'selection',
			'separator',
			'separators',
			'slope',
			'stretchy',
			'subscriptshift',
			'supscriptshift',
			'symmetric',
			'voffset',
			'width',
			'xmlns',

			// XML
			//			'xlink:href',
			'xml:id',
			'xlink:title',
			'xml:space',
			'xmlns:xlink',
		];
		$attrs         = array_map( '__return_true', array_flip( $allowed_attrs ) );
		$allowed_html  = array_map( function() use ( $attrs ) {
			return $attrs;
		}, array_flip( $allowed_tags ) );
	} else {
		global $allowedposttags;
		$allowed_html = $allowedposttags;
		if( wpforo_setting( 'posting', 'content_do_shortcode' ) ) {
			$allowed_html = wp_kses_allowed_html( $key );
		}
		$extra_html                                = wpforo_setting( 'posting', 'extra_html_tags' );
		$allowed_html                              = wpforo_extra_html_parser( $extra_html, $allowed_html );
		$allowed_html['a']['data-gallery']         = true;
		$allowed_html['a']['download']             = true;
		$allowed_html['blockquote']['class']       = true;
		$allowed_html['blockquote']['data-width']  = true;
		$allowed_html['blockquote']['data-userid'] = true;
		$allowed_html['blockquote']['data-postid'] = true;
		$allowed_html['p']['lang']                 = true;
		$allowed_html['p']['dir']                  = true;
		$allowed_html['pre']['contenteditable']    = true;
		// Allow <figure> across core for attachments and GIFs
		$allowed_html['figure'] = [
			'contenteditable' => true,
			'class'           => true,
			'style'           => true,
			'data-*'          => true,
		];
		if( ! wpfval( $allowed_html, 'iframe' ) && class_exists( 'wpForoEmbeds' ) ) {
			$allowed_html['iframe'] = [ 'width' => true, 'height' => true, 'src' => true, 'frameborder' => true, 'allowfullscreen' => true ];
		}
		$allowed_html = apply_filters( 'wpforo_kses_allowed_html', $allowed_html );
	}

	return wp_kses( $string, $allowed_html );
}

function wpforo_deep_merge( $default, $current = [] ) {
	foreach( $default as $k => $v ) {
		if( ! empty( $v ) && is_array( $v ) ) {
			foreach( $v as $kk => $vv ) {
				if( ! empty( $vv ) && is_array( $vv ) ) {
					foreach( $vv as $kkk => $vvv ) {
						if( ! empty( $vvv ) && is_array( $vvv ) ) {
							foreach( $vvv as $kkkk => $vvvv ) {
								if( ! empty( $vvv ) && is_array( $vvv ) ) {
									//Stop on 5th level
								} else {
									if( isset( $current[ $k ][ $kk ][ $kkk ][ $kkkk ] ) ) $default[ $k ][ $kk ][ $kkk ][ $kkkk ] = $current[ $k ][ $kk ][ $kkk ][ $kkkk ];
								}
							}
						} else {
							if( isset( $current[ $k ][ $kk ][ $kkk ] ) ) $default[ $k ][ $kk ][ $kkk ] = $current[ $k ][ $kk ][ $kkk ];
						}
					}
				} else {
					if( isset( $current[ $k ][ $kk ] ) ) $default[ $k ][ $kk ] = $current[ $k ][ $kk ];
				}
			}
		} else {
			if( isset( $current[ $k ] ) ) $default[ $k ] = $current[ $k ];
		}
	}

	return $default;
}

function wpforo_is_image( $e ) {
	return (bool) preg_match( '#^(jpe?g|png|gif|bmp|webp|tiff?)$#i', (string) $e );
}

function wpforo_is_video( $e ) {
	return (bool) preg_match( '#^(mp4|webm|ogg)$#i', (string) $e );
}

function wpforo_is_audio( $e ) {
	return (bool) preg_match( '#^(mp3|wav|ogg)$#i', (string) $e );
}

/**
 * @param string $option
 * @param mixed $default
 * @param bool $cache
 *
 * @return mixed
 */
function wpforo_get_option( $option, $default = null, $cache = true ) {
	if( strpos( (string) $option, 'wpforo_' ) !== 0 ) {
		$option = wpforo_prefix( $option );
	}

	$from_cache = false;
	$value      = null;

	$cache = ( $cache && apply_filters( 'wpforo_options_cache', true ) );

	if( $cache ) {
		$option_file = WPF()->folders['cache']['dir'] . DIRECTORY_SEPARATOR . 'item' . DIRECTORY_SEPARATOR . 'option' . DIRECTORY_SEPARATOR . ( defined( 'AUTH_KEY' ) ? md5( $option . AUTH_KEY ) : md5(
				$option
			) );
		$value       = maybe_unserialize( wpforo_get_file_content( $option_file ) );
		if( ! empty( $value ) ) $from_cache = true;
	}

	if( ! $from_cache ) $value = get_option( $option );

	if( $value === false ) {
		$value = $default;
	} else {
		if( preg_match( '#_(?:general|profiles|rating|tools_misc)$#iu', (string) $option ) ) {
			foreach( [ 'title_groupids', 'title_secondary_groupids', 'rating_title_ug', 'rating_badge_ug', 'admin_bar', 'admin_note_groups', 'admin_note_pages' ] as $k ) {
				if( wpfkey( $default, $k ) ) $default[ $k ] = [];
			}
		}
		if( preg_match( '#_activity$#iu', (string) $option ) ) {
			if( wpfkey( $default, 'enabled_types' ) ) $default['enabled_types'] = [];
		}
		if( preg_match( '#_ai$#iu', (string) $option ) ) {
			foreach( [ 'chatbot_allowed_groups', 'bot_reply_includes' ] as $k ) {
				if( wpfkey( $default, $k ) ) $default[ $k ] = [];
			}
		}
		$default = apply_filters( 'wpforo_get_option_default_arg_before_cast', $default, $option );

		$value = wpforo_settype( $value, gettype( $default ) );
		if( $default && is_array( $default ) && is_array( $value ) ) $value = wpforo_array_args_cast_and_merge( $value, $default );
	}

	if( $cache && ! $from_cache && ! empty( $value ) ) wpforo_write_file( $option_file, maybe_serialize( $value ) );

	return apply_filters( 'wpforo_get_option', $value, $option, $default, $cache );
}

/**
 * calls the WordPress core update_option() function
 *
 * @param string $option Name of the option to update. Expected to not be SQL-escaped.
 * @param mixed $value Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
 * @param string|bool $autoload Optional. Whether to load the option when WordPress starts up. For existing options,
 *                              `$autoload` can only be updated using `update_option()` if `$value` is also changed.
 *                              Accepts 'yes'|true to enable or 'no'|false to disable. For non-existent options,
 *                              the default value is 'yes'. Default null.
 *
 * @return bool True if the value was updated, false otherwise.
 * @since 2.0.0
 */
function wpforo_update_option( $option, $value, $autoload = null ) {
	if( strpos( (string) $option, 'wpforo_' ) !== 0 ) {
		$option = wpforo_prefix( $option );
	}
	$ret = update_option( $option, $value, $autoload );
	do_action( 'wpforo_update_option', $option, $value, $autoload );
	wpforo_clean_cache( 'option' );

	return $ret;
}

/**
 * calls the WordPress core delete_option() function
 *
 * @param string $option Name of the option to update. Expected to not be SQL-escaped.
 *
 * @return bool True if the value was updated, false otherwise.
 * @since 2.0.0
 */
function wpforo_delete_option( $option ) {
	if( strpos( (string) $option, 'wpforo_' ) !== 0 ) {
		$option = wpforo_prefix( $option );
	}
	$ret = delete_option( $option );
	do_action( 'wpforo_delete_option', $option );
	wpforo_clean_cache( 'option' );

	return $ret;
}

/**
 * Extract what remains from an unintentionally truncated serialized string
 * $data contains your original array (or what remains of it).
 *
 * @param string The serialized array
 */
function wpforo_fixSerializedArray( $serialized ) {
	$tmp = preg_replace( '/^a:\d+:\{/', '', (string) $serialized );

	return wpforo_fixSerializedArray_R( $tmp );
}

/**
 * The recursive function that does all the heavy lifing. Do not call directly.
 *
 * @param string The broken serialzized array
 *
 * @return array Returns the repaired string
 */
function wpforo_fixSerializedArray_R( &$broken ) {
	$data  = [];
	$index = null;
	$len   = strlen( (string) $broken );
	$i     = 0;
	while( strlen( (string) $broken ) ) {
		$i ++;
		if( $i > $len ) {
			break;
		}
		if( substr( (string) $broken, 0, 1 ) == '}' ) {
			$broken = substr( (string) $broken, 1 );

			return $data;
		} else {
			$bite = substr( (string) $broken, 0, 2 );
			switch( $bite ) {
				case 's:':
					$re = '/^s:\d+:"([^\"]*)";/';
					if( preg_match( $re, (string) $broken, $m ) ) {
						if( $index === null ) {
							$index = $m[1];
						} else {
							$data[ $index ] = $m[1];
							$index          = null;
						}
						$broken = preg_replace( $re, '', (string) $broken );
					}
				break;
				case 'i:':
					$re = '/^i:(\d+);/';
					if( preg_match( $re, (string) $broken, $m ) ) {
						if( $index === null ) {
							$index = (int) $m[1];
						} else {
							$data[ $index ] = (int) $m[1];
							$index          = null;
						}
						$broken = preg_replace( $re, '', (string) $broken );
					}
				break;
				case 'b:':
					$re = '/^b:[01];/';
					if( preg_match( $re, (string) $broken, $m ) ) {
						$data[ $index ] = (bool) $m[1];
						$index          = null;
						$broken         = preg_replace( $re, '', (string) $broken );
					}
				break;
				case 'a:':
					$re = '/^a:\d+:\{/';
					if( preg_match( $re, (string) $broken, $m ) ) {
						$broken         = preg_replace( '/^a:\d+:\{/', '', (string) $broken );
						$data[ $index ] = wpforo_fixSerializedArray_R( $broken );
						$index          = null;
					}
				break;
				case 'N;':
					$broken         = substr( (string) $broken, 2 );
					$data[ $index ] = null;
					$index          = null;
				break;
			}
		}
	}

	return $data;
}

function wpforo_insert_to_media_library( $attach_path, $title = '' ) {
	if( wpforo_setting( 'posting', 'attachs_to_medialib' ) ) {
		if( ! $attach_path ) return 0;
		$attach_fname = basename( (string) $attach_path );
		if( ! $title ) $title = $attach_fname;
		require_once( ABSPATH . 'wp-admin/includes/media.php' );
		require_once( ABSPATH . 'wp-admin/includes/file.php' );
		require_once( ABSPATH . 'wp-admin/includes/image.php' );
		$filetype   = wp_check_filetype( $attach_fname, null );
		$attachment = [ 'guid' => $attach_path, 'post_mime_type' => $filetype['type'], 'post_title' => $title, 'post_content' => '', 'post_status' => 'inherit' ];
		$attach_id  = wp_insert_attachment( $attachment, $attach_path );
		add_filter( 'intermediate_image_sizes', 'wpforo_attachment_sizes' );
		$attach_data = wp_generate_attachment_metadata( $attach_id, $attach_path );
		wp_update_attachment_metadata( $attach_id, $attach_data );
		remove_filter( 'intermediate_image_sizes', 'wpforo_attachment_sizes' );

		return $attach_id;
	}

	return 0;
}

function wpforo_attachment_sizes( $sizes ) {
	return [ 'thumbnail' ];
}

#################################################################################
/**
 * Cleans forum cache
 *
 * @param string        Item View / Template    (e.g.: 'forum', 'topic', 'post', 'user', 'widget', etc...)
 * @param integer        Item ID                    (e.g.: $topicid or $postid) | (!) ID is 0 on dome actions (e.g.: delete actions)
 * @param array        Item data as array
 *
 * @since 1.2.1
 *
 */
function wpforo_clean_cache( $template = 'all', $id = 0, $item = [] ) {
	if( is_null( WPF()->board ) ) return;
	do_action( 'wpforo_clean_cache_start', $id, $template );

	if( ! $pageid = WPF()->board->get_current( 'pageid' ) ) $pageid = wpforo_wp_url_to_postid( $_SERVER['REQUEST_URI'] );
	if( $pageid ) clean_post_cache( $pageid );

	do_action( 'wpforo_clean_cache', $id, $template );
	WPF()->cache->clean( $id, $template, $item );
	if( $template !== 'option' ) WPF()->statistic_cache_clean();
	do_action( 'wpforo_clean_cache_end', $id, $template );
}

function wpforo_is_owner( $userid, $email = '' ) {
	return wpforo_is_users_same( [ 'userid' => $userid, 'user_email' => $email ] );
}

function wpforo_is_users_same( $user1, $user2 = null ) {
	if( is_null( $user2 ) ) {
		$user2 = WPF()->current_user;
	} elseif( is_scalar( $user2 ) ) {
		if( is_numeric( $user2 ) ) {
			$user2 = [ 'userid' => wpforo_bigintval( $user2 ), 'user_email' => wpforo_member( $user2, 'user_email' ) ];
		} else {
			$user2 = [ 'userid' => 0, 'user_email' => sanitize_email( $user2 ) ];
		}
	}

	if( is_scalar( $user1 ) ) {
		if( is_numeric( $user1 ) ) {
			$user1 = [ 'userid' => wpforo_bigintval( $user1 ), 'user_email' => wpforo_member( $user1, 'user_email' ) ];
		} else {
			$user1 = [ 'userid' => 0, 'user_email' => sanitize_email( $user1 ) ];
		}
	}

	if( wpforo_bigintval( wpfval( $user2, 'userid' ) ) && ( $userid = wpforo_bigintval( wpfval( $user1, 'userid' ) ) ) ) {
		return $userid === $user2['userid'];
	} elseif( wpfval( $user2, 'user_email' ) && ( $user_email = sanitize_email( wpfval( $user1, 'user_email' ) ) ) ) {
		return $user_email === $user2['user_email'];
	}

	return false;
}

function wpforo_dashboard_is_owner() {
	return ( preg_match( '#/profile\.php$#iu', $_SERVER['REQUEST_URI'] ) || ( preg_match( '#/user-edit\.php#iu', $_SERVER['REQUEST_URI'] ) && (int) wpfval( $_GET, 'user_id' ) === WPF(
			)->current_userid ) );
}

/**
 * @param array $user wpforo user array
 * @param bool $echo
 *
 * @return string
 */
function wpforo_user_dname( $user, $echo = false ) {
	$display_name  = trim( (string) wpfval( $user, 'display_name' ) );
	$user_nicename = trim( (string) wpfval( $user, 'user_nicename' ) );
	$dname         = esc_html( $display_name ) ?: ( esc_html( urldecode( $user_nicename ) ) ?: wpforo_phrase( 'Anonymous', false ) );
	$dname         = apply_filters( 'wpforo_user_display_name', $dname, $user );
	if( $echo ) echo $dname;

	return $dname;
}

function wpforo_strlen( $string ) {
	if( ! $string ) return 0;
	if( function_exists( 'mb_strlen' ) ) {
		return mb_strlen( (string) $string );
	} else {
		return strlen( (string) $string );
	}
}

function wpforo_string2array( $string, $regexp = '' ) {
	if( ! $regexp ) $regexp = '#' . preg_quote( PHP_EOL ) . '#isu';
	$array = preg_split( $regexp, $string );

	return array_filter( $array );
}

function wpforo_array_ordered_intersect_key( $array1, $array2 ) {
	$new_array = [];
	foreach( $array2 as $key => $value ) if( wpfkey( $array1, $key ) ) $new_array[ $key ] = $array1[ $key ];

	return $new_array;
}

function wpforo_get_upload_dir_folders() {
	return (array) array_diff( scandir( WPF()->folders['upload']['dir'] ), [ '.', '..' ] );
}

function wpforo_fix_upload_dir( $upload_dir ) {
	$folders = wpforo_ram_get( 'wpforo_get_upload_dir_folders' );
	$folders = array_map( 'preg_quote', $folders );
	if( $folders && preg_match( '#[/\\\]wpforo(?:_\d+)?[/\\\](?:' . implode( '|', $folders ) . ')[/\\\].+?$#iu', (string) $upload_dir, $match ) ) {
		$upload_dir = wpforo_fix_dir_sep( WPF()->folders['wp_upload']['dir'] . $match[0] );
		$upload_dir = urldecode( (string) $upload_dir );
	} else {
		// Security: Return empty string for paths outside wpforo uploads to prevent arbitrary file operations
		$upload_dir = '';
	}

	return $upload_dir;
}

function wpforo_fix_upload_url( $upload_url ) {
	$folders = wpforo_ram_get( 'wpforo_get_upload_dir_folders' );
	$folders = array_map( 'preg_quote', $folders );
	if( $folders && preg_match( '#[/\\\]wpforo(?:_\d+)?[/\\\](?:' . implode( '|', $folders ) . ')[/\\\].+?$#iu', (string) $upload_url, $match ) ) {
		$upload_url = wpforo_fix_url_sep( WPF()->folders['wp_upload']['url'] . $match[0] );
	}

	return $upload_url;
}

function wpforo_xcopy( $source, $dest ) {
	// Check for symlinks
	if( is_link( $source ) ) {
		return symlink( readlink( $source ), $dest );
	}

	// Simple copy for a file
	if( is_file( $source ) ) {
		return copy( $source, $dest );
	}

	// Make destination directory
	if( ! is_dir( $dest ) ) {
		wp_mkdir_p( $dest );
	}

	// Loop through the folder
	$dir = dir( $source );
	while( false !== $entry = $dir->read() ) {
		// Skip pointers
		if( $entry === '.' || $entry === '..' ) {
			continue;
		}

		// Deep copy directories
		wpforo_xcopy( rtrim( (string) $source, '/' ) . "/$entry", rtrim( (string) $dest, '/' ) . "/$entry" );
	}

	// Clean up
	$dir->close();

	return true;
}

function wpforo_printf_array( $format, $arr ) {
	return call_user_func_array( 'printf', array_merge( (array) $format, (array) $arr ) );
}

function wpforo_sprintf_array( $format, $arr ) {
	return call_user_func_array( 'sprintf', array_merge( (array) $format, (array) $arr ) );
}

function wpforo_avatar_url( $avatar_html ) {
	if( preg_match( '#src=[\'"]([^\'"]+?)[\'"]#iu', (string) $avatar_html, $matches ) ) {
		return $matches[1];
	}

	return '';
}

/**
 * @param string $content
 * @param bool $first
 * @param string $type
 *
 * @return string|array
 */
function wpforo_find_image_urls( $content, $first = true, $type = 'general' ) {
	$images  = [];
	$content = trim( (string) $content );

	if( $content ) {
		if( preg_match_all( '#<img[^<>]*?src=[\'\"]([^\'\"]+\.(?:jpe?g|png|gif|bmp|webp|svg|tiff))[\'\"][^<>]*?>#iu', (string) $content, $matches, PREG_SET_ORDER ) ) {
			foreach( $matches as $match ) {
				if( preg_match( '#class=[\'\"]wpfem[^\'\"]*[\'\"][^<>]*?data-code=#isu', $match[0] ) ) continue;
				if( strpos( (string) $match[1], 'http' ) === 0 ) {
					$images[] = $match[1];
				} else {
					$images[] = 'http' . ( is_ssl() ? 's' : '' ) . ':' . $match[1];
				}
			}
		} elseif( preg_match_all( '#https?://[^\r\n\t\s\'\"<>]+?\.(?:jpe?g|png|gif|bmp|webp|svg|tiff)#iu', (string) $content, $matches, PREG_SET_ORDER ) ) {
			foreach( $matches as $match ) {
				$images[] = $match[0];
			}
		} elseif( preg_match_all( '#//[^\r\n\t\s\'\"<>]+?\.(?:jpe?g|png|gif|bmp|webp|svg|tiff)#iu', (string) $content, $matches, PREG_SET_ORDER ) ) {
			foreach( $matches as $match ) {
				$images[] = 'http' . ( is_ssl() ? 's' : '' ) . ':' . $match[0];
			}
		}
	}

	if( $first && $images ) $images = wpfval( $images, 0 );

	return apply_filters( 'wpforo_find_image_url', $images, $type, $first );
}

function wpforo_is_json( $string ) {
	if( is_scalar( $string ) ) {
		json_decode( $string );

		return json_last_error() === JSON_ERROR_NONE;
	}

	return false;
}

function wpforo_ajax_response( $message ) {
	wp_send_json( $message );
	die();
}

function wpforo_unique_username( $username ) {
	static $i;
	if( ! $username ) $username = 'user_' . uniqid();
	if( strpos( (string) $username, '@' ) !== false ) {
		$parts = explode( "@", $username );
		if( ! empty( $parts ) && isset( $parts[0] ) && $parts[0] ) {
			$username = $parts[0];
		} else {
			$username = str_replace( '@', '', $username );
		}
	}
	if( null === $i ) {
		$i = 1;
	} else {
		$i ++;
	}
	if( ! username_exists( $username ) ) {
		return $username;
	}
	$new_username = sprintf( '%s-%s', $username, $i );
	if( ! username_exists( $new_username ) ) {
		return $new_username;
	} else {
		return call_user_func( __FUNCTION__, $username );
	}
}

function wpforo_is_session_started() {
	if( php_sapi_name() !== 'cli' ) {
		if( version_compare( phpversion(), '5.4.0', '>=' ) ) {
			return session_status() === PHP_SESSION_ACTIVE ? true : false;
		} else {
			return session_id() === '' ? false : true;
		}
	}

	return false;
}

function wpforo_current_guest( $email ) {
	$guest = WPF()->member->get_guest_cookies();
	if( ! wpfval( $guest, 'email' ) || ! $guest['email'] ) return false;
	if( $email == $guest['email'] ) {
		return true;
	} else {
		return false;
	}
}

function wpforo_extra_html_parser( $extra_html = '', $allowed_html = [] ) {
	if( $extra_html ) {
		$extra_html = explode( ',', $extra_html );
		$extra_html = array_filter( $extra_html );
		if( ! empty( $extra_html ) ) {
			foreach( $extra_html as $html ) {
				$html = trim( (string) $html );
				if( preg_match( '|([^()]+)\((.+)\)|', $html, $item ) ) {
					if( wpfval( $item, 1 ) && wpfval( $item, 2 ) ) {
						$attrs = explode( ' ', $item[2] );
						$attrs = array_map( 'trim', $attrs );
						foreach( $attrs as $attr ) {
							$allowed_html[ $item[1] ][ $attr ] = [];
						}
					}
				} else {
					$allowed_html[ $html ] = [];
				}
			}
		}
	}

	return $allowed_html;
}

function wpforo_clear_array( $array, $clear = [], $by = 'value' ) {
	if( is_array( $clear ) && ! empty( $clear ) ) {
		foreach( $clear as $ext ) {
			if( $by == 'value' ) {
				if( ( $key = array_search( $ext, $array ) ) !== false ) {
					unset( $array[ $key ] );
				}
			} elseif( $by == 'key' ) {
				if( wpfkey( $array, $ext ) ) unset( $array[ $ext ] );
			}
		}
	} elseif( is_string( $clear ) || is_numeric( $clear ) ) {
		if( wpfval( $array, $clear ) ) unset( $array[ $clear ] );
	}

	return $array;
}

function wpforo_key( $array = [], $value = '', $type = 'default' ) {
	$keys = [];
	if( is_array( $array ) && ! empty( $array ) ) {
		foreach( $array as $k => $v ) {
			if( $v === $value ) {
				$keys[] = $k;
			}
		}
	}
	if( $type === 'sort' ) {
		sort( $keys );

		return $keys;
	} else {
		return $keys;
	}
}

function wpforo_unslashe( $data ) {
	$data = is_array( $data ) ? array_map( 'wpforo_unslashe', $data ) : stripslashes( (string) $data );

	return $data;
}

function wpforo_encode( $data ) {
	$data = is_array( $data ) ? array_map( 'wpforo_encode', $data ) : htmlspecialchars( $data, ENT_QUOTES );

	return $data;
}

function wpforo_decode( $data ) {
	$data = is_array( $data ) ? array_map( 'wpforo_decode', $data ) : htmlspecialchars_decode( $data, ENT_QUOTES );

	return $data;
}

function wpforo_trim( $data ) {
	$data = is_array( $data ) ? array_map( 'wpforo_trim', $data ) : trim( (string) $data );

	return $data;
}

function wpforo_sanitize_int( $data ) {
	$data = is_array( $data ) ? array_map( 'wpforo_sanitize_int', $data ) : intval( $data );

	return $data;
}

function wpforo_sanitize_text( $data ) {
	$data = is_array( $data ) ? array_map( 'wpforo_sanitize_text', $data ) : sanitize_text_field( $data );

	return $data;
}

/**
 * Sanitize orderby parameter for SQL queries.
 * Validates against a whitelist of allowed column names to prevent SQL injection.
 *
 * @param string $orderby The orderby value to sanitize
 * @param string $context The context: 'topics', 'posts', 'members', or 'search'
 * @param string $default Default value if validation fails
 * @return string Sanitized orderby value
 */
function wpforo_sanitize_orderby( $orderby, $context = 'topics', $default = '' ) {
	$orderby = sanitize_text_field( $orderby );

	// Define allowed columns for each context
	$allowed = [
		'topics' => [
			'topicid', 'forumid', 'userid', 'title', 'slug', 'created', 'modified',
			'views', 'posts', 'type', 'status', 'private', 'closed', 'solved',
			'has_attach', 'first_postid', 'last_postid', 'pollid', 'prefix'
		],
		'posts' => [
			'postid', 'forumid', 'topicid', 'userid', 'title', 'created', 'modified',
			'status', 'private', 'is_answer', 'is_first_post', 'votes', 'root', 'parentid'
		],
		'members' => [
			'userid', 'posts', 'questions', 'answers', 'comments', 'reactions', 'points',
			'online_time', 'registered', 'display_name', 'user_registered'
		],
		'search' => [
			'relevancy', 'date', 'user', 'forum', 'created', 'modified'
		],
	];

	// Get the whitelist for this context
	$whitelist = isset( $allowed[$context] ) ? $allowed[$context] : [];

	// Also allow common aliases
	$whitelist = array_merge( $whitelist, [ 'id', 'date', 'name' ] );

	// Check if orderby is in the whitelist (case-insensitive)
	$orderby_lower = strtolower( $orderby );
	foreach( $whitelist as $allowed_col ) {
		if( strtolower( $allowed_col ) === $orderby_lower ) {
			return $allowed_col; // Return the properly cased version
		}
	}

	// If not in whitelist, return the default
	return $default;
}

if( ! function_exists( 'sanitize_textarea_field' ) && ! function_exists( '_sanitize_text_fields' ) ) {
	function sanitize_textarea_field( $str ) {
		$filtered = _sanitize_text_fields( $str, true );

		return apply_filters( 'sanitize_textarea_field', $filtered, $str );
	}

	function _sanitize_text_fields( $str, $keep_newlines = false ) {
		$filtered = wp_check_invalid_utf8( $str );
		if( strpos( (string) $filtered, '<' ) !== false ) {
			$filtered = wp_pre_kses_less_than( $filtered );
			$filtered = wp_strip_all_tags( $filtered, false );
			$filtered = str_replace( "<\n", "&lt;\n", $filtered );
		}
		if( ! $keep_newlines ) {
			$filtered = preg_replace( '/[\r\n\t ]+/', ' ', (string) $filtered );
		}
		$filtered = trim( (string) $filtered );
		$found    = false;
		while( preg_match( '/%[a-f0-9]{2}/i', (string) $filtered, $match ) ) {
			$filtered = str_replace( $match[0], '', $filtered );
			$found    = true;
		}
		if( $found ) {
			$filtered = trim( preg_replace( '/ +/', ' ', (string) $filtered ) );
		}

		return $filtered;
	}
}

/**
 * @param string $role
 *
 * @return bool
 */
function wpforo_current_user_is( $role ) {
	$role = strtolower( (string) $role );

	$filter_result = apply_filters( 'wpforo_current_user_is', null, $role );
	if( ! is_null( $filter_result ) ) return (bool) $filter_result;

	switch( $role ) {
		case 'admin':
			if( current_user_can( 'activate_plugins' ) ) {
				return true;
			}
			if( current_user_can( 'install_plugins' ) ) {
				return true;
			}
			if( current_user_can( 'create_sites' ) ) {
				return true;
			}
		break;
		case 'moderator':
			if( WPF()->usergroup->can( 'aum' ) ) {
				return true;
			}
			if( current_user_can( 'moderate_comments' ) ) {
				return true;
			}
			if( current_user_can( 'edit_published_posts' ) ) {
				return true;
			}
			if( current_user_can( 'manage_categories' ) ) {
				return true;
			}
		break;
	}

	return false;
}

/**
 * @param int $userid
 * @param string $role
 *
 * @return bool
 */
function wpforo_user_is( $userid, $role ) {
	$userid = wpforo_bigintval( $userid );
	$role   = strtolower( (string) $role );

	$filter_result = apply_filters( 'wpforo_user_is', null, $userid, $role );
	if( ! is_null( $filter_result ) ) return (bool) $filter_result;

	switch( $role ) {
		case 'admin':
			if( user_can( $userid, 'activate_plugins' ) ) {
				return true;
			}
			if( user_can( $userid, 'install_plugins' ) ) {
				return true;
			}
			if( user_can( $userid, 'create_sites' ) ) {
				return true;
			}
			if( $user = WPF()->member->get_member( $userid ) ) {
				if( WPF()->usergroup->can( 'mf', $user['groupids'] ) || WPF()->usergroup->can( 'ms', $user['groupids'] ) ) {
					return true;
				}
			}
		break;
		case 'moderator':
			if( user_can( $userid, 'moderate_comments' ) ) {
				return true;
			}
			if( user_can( $userid, 'edit_published_posts' ) ) {
				return true;
			}
			if( user_can( $userid, 'manage_categories' ) ) {
				return true;
			}
			if( $user = WPF()->member->get_member( $userid ) ) {
				if( WPF()->usergroup->can( 'aum', $user['groupids'] ) ) {
					return true;
				}
			}
		break;
	}

	return false;
}

function wpforo_random_colors() {
	mt_srand( (int) ( microtime( true ) * 1000000 ) );
	$color = '';
	while( strlen( (string) $color ) < 6 ) {
		$color .= sprintf( "%02X", mt_rand( 0, 255 ) );
	}

	return '#' . $color;
}

/**
 * @param string $dir
 *
 * @return string
 */
function wpforo_fix_dir_sep( string $dir ): string {
	return WPF()->fix_dir_sep( $dir );
}

/**
 * @param string $url
 *
 * @return string
 */
function wpforo_fix_url_sep( string $url ): string {
	return WPF()->fix_url_sep( $url );
}

function wpforo_root_exist() {
	$args = [ 'table' => WPF()->tables->posts, 'col' => 'root', 'check' => 'col_exists' ];

	return wpforo_db_check( $args );
}

function wpforo_urlencode( $str ): string {
	$str = (string) $str;
	if( ! preg_match( '#^(\#post-\d+|https?:|s?ftp:)$#iu', (string) $str ) && ! preg_match( '#([?&][^?&/=\r\n]*=?[^?&/=\r\n]*)(?1)*$#iu', (string) $str ) && strpos(
		                                                                                                                                                         (string) $str,
		                                                                                                                                                         '~'
	                                                                                                                                                         ) === false && strpos(
		                                                                                                                                                                        (string) $str,
		                                                                                                                                                                        '*'
	                                                                                                                                                                        ) === false && $str === urldecode(
			(string) $str
		) ) {
		$str = urlencode( (string) $str );
	}

	// Use wpforo_url_strtolower() instead of strtolower() to keep the exact values
	// of GET variables in pagination buttons. Otherwise, the GET values will differ
	// and the field value will be changed on the next page.
	// Example: on members search page the selected "field=Test" will be changed to "field=test"
	// on the next page. And the select field will loos current selected value.
	return $str !== urldecode( (string) $str ) ? wpforo_url_strtolower( (string) $str ) : $str;
}

/**
 * Converts the URL to lowercase.
 *
 * This function converts the URL to lowercase. If the $include_query parameter is set to true,
 * the entire URL including the query string is converted to lowercase. Otherwise, only the part of the URL
 * before the query string is converted to lowercase.
 *
 * @param string $url The URL to be converted to lowercase.
 * @param bool $include_query Optional. Whether to include the query string in the conversion. Default is false.
 *
 * @return string The URL converted to lowercase.
 */
function wpforo_url_strtolower( $url, $include_query = false ): string {
	// If $include_query is true, convert the entire URL to lowercase and return it.
	if( $include_query ) return strtolower( $url );
	// If the URL does not contain a query string, convert the entire URL to lowercase and return it.
	if( strpos( $url, '?' ) === false ) {
		$url = strtolower( $url );
	} else {
		// If the URL contains a query string, split the URL into two parts at the '?' character.
		$parts = explode( '?', $url );
		// Remove the last element from the $parts array and store it in $query.
		$query = array_pop( $parts );
		$parts = array_map( 'strtolower', $parts );
		$url   = implode( '?', $parts ) . '?' . $query;
	}

	return $url;
}

function wpforo_fix_url( string $url ): string {
	$hash = '';
	$url  = preg_replace_callback( '#(\#[^/]*)$#iu', function( $m ) use ( &$hash ) {
		$hash = $m[1];

		return '';
	},                             $url, 1 );

	if( preg_match( '#^((?:https?:|s?ftp:)//)([^?&/=\s:]+)((?::\d+)?)([^\r\n]*)#iu', (string) $url, $match ) ) {
		$match[2] = wpforo_urlencode( $match[2] );
		$url      = $match[1] . $match[2] . $match[3] . implode( '/', array_map( 'wpforo_urlencode', explode( '/', $match[4] ) ) );
	} else {
		$url = implode( '/', array_map( 'wpforo_urlencode', explode( '/', $url ) ) );
	}

	return $url . $hash;
}

function wpforo_is_domains_equal( $url1, $url2 ) {
	$domain1 = strtolower( str_replace( 'www.', '', parse_url( $url1, PHP_URL_HOST ) ) );
	$domain2 = strtolower( str_replace( 'www.', '', parse_url( $url2, PHP_URL_HOST ) ) );

	return $domain1 === $domain2;
}

function wpforo_is_url_internal( $url, $home_url = null ) {
	$url = trim( (string) $url );
	if( ! preg_match( '#^(?:https?:)?//#iu', (string) $url ) ) return true;
	if( ! $home_url ) $home_url = home_url();
	$home_url = trim( (string) $home_url );
	$url      = preg_replace( '#^(https?://)?(www\.)?#iu', '', (string) $url );
	$home_url = preg_replace( [ '#^(https?://)?(www\.)?#iu', '#/?\?.*$#isu', '#index\.php/?#iu' ], '', (string) $home_url );

	return strpos( (string) $url, $home_url ) === 0;
}

function wpforo_is_url_external( $url, $home_url = null ) {
	return ! wpforo_is_url_internal( $url, $home_url );
}

function wpforo_settype( $var, $type ) {
	$var_type      = strtolower( gettype( $var ) );
	$type          = strtolower( (string) $type );
	$allowed_types = [ 'bool', 'boolean', 'int', 'integer', 'double', 'real', 'float', 'string', 'array', 'object', 'null' ];
	if( $var_type !== $type && in_array( $var_type, $allowed_types ) && in_array( $type, $allowed_types ) && $type !== 'null' && ! ( $var_type === 'object' && ! in_array( $type, [ 'boolean', 'array' ]
			) ) && ! ( $var_type === 'array' && $type === 'string' ) ) {
		settype( $var, $type );
	}

	return $var;
}

function wpforo_array_args_cast( $array, $type ) {
	return array_map( function( $var ) use ( $type ) {
		return wpforo_settype( $var, $type );
	}, $array );
}

function wpforo_array_args_cast_and_merge( $array, $default ) {
	$array   = (array) $array;
	$default = (array) $default;
	foreach( $array as $key => $value ) {
		if( array_key_exists( $key, $default ) ) {
			$array[ $key ] = wpforo_settype( $value, gettype( $default[ $key ] ) );
			if( is_array( $array[ $key ] ) ) {
				$array[ $key ] = wpforo_array_args_cast_and_merge(
					$array[ $key ],
					$default[ $key ]
				);
			} #### do not open this comment until you have checkboxes on options settings pages
		}
	}
	$array += $default;

	return $array;
}

function wpforo_db_data_format( $var ) {
	switch( gettype( $var ) ) {
		case 'bool':
		case 'boolean':
		case 'int':
		case 'integer':
			return '%d';
		case 'double':
		case 'real':
		case 'float':
			return '%f';
		default:
			return '%s';
	}
}

/**
 * @param int $seconds execution time in seconds , by default 0 (unlimited)
 */
function wpforo_set_max_execution_time( $seconds = 0 ) {
	if( function_exists( 'set_time_limit' ) ) @set_time_limit( $seconds );
	if( function_exists( 'ini_set' ) ) @ini_set( 'max_execution_time', $seconds );
}

/**
 * @param string $pattern
 * @param array $array
 *
 * @return array
 */
function wpforo_preg_grep_recursive( $pattern, $array ) {
	$m = [];
	if( is_array( $array ) ) {
		foreach( $array as $key => $value ) {
			if( is_string( $value ) || is_numeric( $value ) ) {
				if( preg_match( $pattern, (string) $value ) ) $m[ $key ] = $value;
			} elseif( is_array( $value = wpforo_settype( $value, 'array' ) ) ) {
				if( $_m = wpforo_preg_grep_recursive( $pattern, $value ) ) $m[ $key ] = $_m;
			}
		}
	}

	return $m;
}

function wpforo_send_new_user_notifications( $userid, $notify = 'both' ) {
	if( wpforo_setting( 'email', 'disable_new_user_admin_notification' ) ) {
		if( $notify !== 'admin' ) wp_send_new_user_notifications( $userid, 'user' );
	} else {
		wp_send_new_user_notifications( $userid, $notify );
	}
}

function wpforo_get_callbacks_for_action( $hook = '' ) {
	global $wp_filter;
	if( empty( $hook ) || ! isset( $wp_filter[ $hook ] ) ) return [];

	return $wp_filter[ $hook ]->callbacks;
}

/**
 * The optimized version of wordpress url_to_postid($url) function
 *
 * Examine a URL and try to determine the post ID it represents.
 *
 * Checks are supposedly from the hosted site blog.
 *
 * @param string $url Permalink to check.
 *
 * @return int Post ID, or 0 on failure.
 * @global WP $wp Current WordPress environment instance.
 *
 * @since 1.7.4
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 */
function _wpforo_wp_url_to_postid( $url ) {
	if( strpos( (string) $url, admin_url() ) !== false ) return 0;

	$url_host      = str_replace( 'www.', '', (string) parse_url( $url, PHP_URL_HOST ) );
	$home_url_host = str_replace( 'www.', '', (string) parse_url( home_url(), PHP_URL_HOST ) );

	// Bail early if the URL does not belong to this site.
	if( $url_host && $url_host !== $home_url_host ) return 0;

	// First, check to see if there is a 'p=N' or 'page_id=N' to match against.
	if( preg_match( '#[?&](p|page_id|post_id|post|attachment_id)=(\d+)#', (string) $url, $values ) ) {
		if( $id = absint( $values[2] ) ) return $id;
	}

	global $wp_rewrite;
	if( $wp_rewrite ) {
		// Get rid of the #anchor.
		$url_split = explode( '#', $url );
		$url       = $url_split[0];

		// Get rid of URL ?query=string.
		$url_split = explode( '?', $url );
		$url       = $url_split[0];

		// Set the correct URL scheme.
		$scheme = parse_url( home_url(), PHP_URL_SCHEME );
		$url    = set_url_scheme( $url, $scheme );

		// Add 'www.' if it is absent and should be there.
		if( false !== strpos( home_url(), '://www.' ) && false === strpos( $url, '://www.' ) ) {
			$url = str_replace( '://', '://www.', $url );
		}

		// Strip 'www.' if it is present and shouldn't be.
		if( false === strpos( home_url(), '://www.' ) ) {
			$url = str_replace( '://www.', '://', $url );
		}

		if( trim( (string) $url, '/' ) === home_url() && 'page' == get_option( 'show_on_front' ) ) {
			$page_on_front = get_option( 'page_on_front' );
			if( $page_on_front && get_post( $page_on_front ) instanceof WP_Post ) return (int) $page_on_front;
		}

		// Check to see if we are using rewrite rules.
		$rewrite = $wp_rewrite->wp_rewrite_rules();

		// Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options.
		if( empty( $rewrite ) ) return 0;

		// Strip 'index.php/' if we're not using path info permalinks.
		if( ! $wp_rewrite->using_index_permalinks() ) {
			$url = str_replace( $wp_rewrite->index . '/', '', $url );
		}

		if( false !== strpos( trailingslashit( (string) $url ), home_url( '/' ) ) ) {
			// Chop off http://domain.com/[path].
			$url = str_replace( home_url(), '', $url );
		} else {
			// Chop off /path/to/blog.
			$home_path = parse_url( home_url( '/' ) );
			$home_path = isset( $home_path['path'] ) ? $home_path['path'] : '';
			$url       = preg_replace( sprintf( '#^%s#', preg_quote( $home_path ) ), '', trailingslashit( (string) $url ) );
		}

		// Trim leading and lagging slashes.
		$url = trim( (string) $url, '/' );

		$request              = $url;
		$post_type_query_vars = [];

		foreach( get_post_types( [], 'objects' ) as $post_type => $t ) {
			if( ! empty( $t->query_var ) ) {
				$post_type_query_vars[ $t->query_var ] = $post_type;
			}
		}

		// Look for matches.
		$request_match = $request;
		foreach( (array) $rewrite as $match => $query ) {
			if( preg_match( "#^$match#", (string) $request_match, $matches ) ) {

				if( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)]/', (string) $query, $varmatch ) ) {
					// This is a verbose page match, let's check to be sure about it.
					$page = get_page_by_path( $matches[ $varmatch[1] ] );
					if( ! $page ) {
						continue;
					}

					$post_status_obj = get_post_status_object( $page->post_status );
					if( ! $post_status_obj->public && ! $post_status_obj->protected && ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
						continue;
					}
				}

				// Got a match.
				// Trim the query of everything up to the '?'.
				$query = preg_replace( '!^.+\?!', '', (string) $query );

				// Substitute the substring matches into the query.
				$query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) );

				// Filter out non-public query vars.
				global $wp;
				parse_str( $query, $query_vars );
				$query = [];
				foreach( (array) $query_vars as $key => $value ) {
					if( in_array( $key, $wp->public_query_vars ) ) {
						$query[ $key ] = $value;
						if( isset( $post_type_query_vars[ $key ] ) ) {
							$query['post_type'] = $post_type_query_vars[ $key ];
							$query['name']      = $value;
						}
					}
				}

				// Resolve conflicts between posts with numeric slugs and date archive queries.
				$query = wp_resolve_numeric_slug_conflicts( $query );

				// Do the query.
				if( $p = wpforo_bigintval( wpfval( $query, 'p' ) ) ) return $p;
				if( $page_id = wpforo_bigintval( wpfval( $query, 'page_id' ) ) ) return $page_id;

				$pagename = wpfval( $query, 'pagename' );
				$name     = wpfval( $query, 'name' );
				if( $pagename || $name ) {
					if( ! $slug = $pagename ) $slug = $name;
					$sql        = "SELECT `ID` FROM `" . WPF()->db->posts . "`
			        WHERE `post_status` = 'publish'
			        AND `post_name` = %s";
					$post_types = (array) apply_filters( 'wpforo_wp_url_to_postid_post_types', [ 'page', 'post' ] );
					if( $post_types ) $sql .= " AND `post_type` IN('" . implode( "','", $post_types ) . "')";
					$sql    = WPF()->db->prepare( $sql, $slug );
					$postid = WPF()->db->get_var( $sql );
					if( $postid = wpforo_bigintval( $postid ) ) {
						return $postid;
					} else {
						return 0;
					}
				} else {
					return 0;
				}

			}
		}
	}

	return 0;
}

function wpforo_wp_url_to_postid( $url ) {
	return wpforo_ram_get( '_wpforo_wp_url_to_postid', $url );
}

function wpforo_get_blog_content_types() {
	$post_types         = get_post_types( [
		                                      'public'             => true,
		                                      'publicly_queryable' => true,
		                                      'show_ui'            => true,
		                                      'capability_type'    => 'post',
	                                      ] );
	$post_types['post'] = 'post';
	$post_types['page'] = 'page';
	unset( $post_types['attachment'] );
	$post_types = array_filter( array_keys( $post_types ) );

	return apply_filters( 'wpforo_get_blog_content_types', $post_types );
}

/**
 * @param string $css
 *
 * @return string
 */
function wpforo_add_wrapper( $css ) {
	$css = preg_replace( '@(#(?:wpforo-wrap|wpfa_dialog|wpfa_dialog_wrap)[\s.#{:>+\[~,])@um', '#wpforo $1', (string) $css );

	return preg_replace( '@(#(?:wpforo|wpforo-wrap|wpfa_dialog|wpfa_dialog_wrap)\s*)#wpforo[\s.#{:>+\[~,]@um', '$1', (string) $css );
}

/**
 * @param string $css
 *
 * @return string
 */
function wpforo_wrap_fix_in_css( $css ) {
	if( ( strpos( (string) $css, '#wpforo-wrap' ) !== false && strpos( (string) $css, '#wpforo #wpforo-wrap' ) === false ) || ( strpos( (string) $css, '#wpfa_dialog' ) !== false && strpos(
		                                                                                                                                                                                 (string) $css,
		                                                                                                                                                                                 '#wpforo #wpfa_dialog'
	                                                                                                                                                                                 ) === false ) ) {
		$css = wpforo_add_wrapper( $css );
	}

	return $css;
}

/**
 * @param string $csspath
 */
function wpforo_wrap_fix_in_cssfile( $csspath ) {
	$csspath = wpforo_fix_dir_sep( $csspath );
	if( is_file( $csspath ) ) {
		$css = wpforo_get_file_content( $csspath );
		$css = wpforo_wrap_fix_in_css( $css );
		if( md5( $css ) !== md5_file( $csspath ) ) wpforo_write_file( $csspath, $css );
	}
}

function wpforo_wrap_in_all_addons_css() {
	$csspaths = [
		'/wpforo-ad-manager/assets/css/style.css',
		'/wpforo-advanced-attachments/assets/css/style.css',
		'/wpforo-cross-posting/assets/css/wpdiscuz-uploader.css',
		'/wpforo-cross-posting/assets/css/wpforo-cross-rtl.css',
		'/wpforo-cross-posting/assets/css/wpforo-cross.css',
		'/wpforo-embeds/assets/css/embed.css',
		'/wpforo-emoticons/assets/emoticons.css',
		'/wpforo-mycred/css/mycread.css',
		'/wpforo-polls/assets/css/poll.css',
		'/wpforo-private-messages/assets/css/style-rtl.css',
		'/wpforo-private-messages/assets/css/style.css',
		'/wpforo-user-custom-fields/assets/css/frontend.css',
	];
	foreach( $csspaths as $csspath ) wpforo_wrap_fix_in_cssfile( WP_PLUGIN_DIR . $csspath );

	WPF()->dissmissed['addons_css_update'] = 1;
	wpforo_update_option( 'dissmissed', WPF()->dissmissed );
}

/*add_action( 'admin_notices', function() {
	if( ! (int) wpfval( WPF()->dissmissed, 'addons_css_update' ) ) {
		$has_addon = false;
		foreach( wpforo_get_addons_info() as $addon ) {
			if( class_exists( $addon['class'] ) ) {
				$has_addon = true;
				break;
			}
		}
		if( ! $has_addon ) return;
		$class   = 'notice notice-warning';
		$message = '<h3>' . __( 'Action Required!', 'wpforo' ) . '<span style="display: inline-block;font-size: 14px; padding: 0 7px; font-weight: normal;">' . __( 'Please update wpForo addons CSS style to make compatible with the current version of wpForo.', 'wpforo' ) . '</span>' . '</h3>';
		$message .= '<a href="' . admin_url( wp_nonce_url( 'admin.php?page=' . wpforo_prefix_slug( 'settings' ) . '&wpfaction=update_addons_css', 'wpforo-update-addons-css' ) ) . '" class="button button-primary">' . __( 'Update CSS >>', 'wpforo' ) . '</a>';
		printf( '<div class="%1$s"><p>%2$s</p></div>', $class, $message );
	}
} );*/

/**
 * @param string $url
 *
 * @return string|null
 */
function wpforo_get_topic_slug_from_url( $url = '' ) {
	$url = trim( (string) $url );
	if( ! $url ) $url = wpforo_get_request_uri();

	if( is_wpforo_url( $url ) ) {
		if( preg_match( '#/' . preg_quote( wpforo_settings_get_slug( 'postid' ) ) . '/(\d+)/?$#isu', strtok( $url, '?' ), $match ) ) {
			$url = WPF()->post->get_url( $match[1] );
		}
		if( preg_match( '#^[\r\n\t\s]*https?://[^\r\n\t\s]+?/[^/]+?/([^/]+?)(?:/' . wpforo_settings_get_slug( 'paged' ) . '/\d+/?)?(?:/?\#post-\d+)?/?[\r\n\t\s]*$#isu', (string) $url, $match ) ) {
			return $match[1];
		}
	}

	return null;
}

function wpforo_clean_folder( $directory ) {
	$directory    = wpforo_fix_dir_sep( $directory );
	$directory_ns = trim( (string) $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR . '*';
	$directory_ws = DIRECTORY_SEPARATOR . trim( (string) $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR . '*';
	$glob         = glob( $directory_ns );
	if( empty( $glob ) ) $glob = glob( $directory_ws );
	foreach( $glob as $item ) {
		if( strpos( (string) $item, 'index.html' ) !== false || strpos( (string) $item, '.htaccess' ) !== false ) continue;
		if( ! is_dir( $item ) && file_exists( $item ) ) {
			@unlink( $item );
		}
	}
}

/**
 * @param string $basename
 *
 * @return string
 */
function wpforo_fix_table_name( $basename ) {
	return WPF()->fix_table_name( $basename );
}

/**
 * @param callable $func
 * @param mixed ... $_, ... [optional] call_user_func parameters
 *
 * @return mixed
 */
function wpforo_ram_get( $func ) {
	return call_user_func_array( [ WPF()->ram_cache, 'call_user_func' ], func_get_args() );
}

function wpforo_prefix( $str = '', $wp_prefix = false ) {
	return ( $wp_prefix ? WPF()->blog_prefix : '' ) . WPF()->prefix . $str;
}

function wpforo_prefix_slug( $str = '' ) {
	if( $str === 'dashboard' && ! is_wpforo_multiboard() ) $str = 'overview';

	return str_replace( '_', '-', wpforo_prefix( $str ) );
}

function wpforo_get_ajax_url() {
	return admin_url(
		sprintf(
			'admin-ajax.php?lang=%1$s&page_id=%2$d&wpforo_boardid=%3$d',
			strtok( WPF()->locale, '_' ),
			WPF()->board->get_current( 'pageid' ),
			WPF()->board->get_current( 'boardid' )
		)
	);
}

function wpforo_get_query_var_lang() {
	if( ! $lang = wpfval( $_GET, 'lang' ) ) $lang = get_query_var( 'lang' );

	return $lang;
}

function wpforo_is_db_mysql8() {
	return wpforo_ram_get( [ WPF(), 'is_db_mysql8' ] );
}

function wpforo_is_module_enabled( $module_key, $board = [] ) {
	return WPF()->board->is_module_enabled( $module_key, $board );
}

function is_wpforo_multiboard() {
	return wpforo_ram_get( [ WPF()->board, 'is_multi' ] );
}

function wpforo_get_redirect_to() {
	if( ! ( ( $redirect_to = urldecode( (string) wpfval( $_GET, 'redirect_to' ) ) ) && wpforo_is_url_internal( $redirect_to ) ) ) {
		$redirect_to = preg_replace( '#/?\?.*$#isu', '', wpforo_get_request_uri() );
	}

	return $redirect_to;
}

function wpforo_get_redirect_to_url_path( $redirect_to = null ) {
	if( is_null( $redirect_to ) ) $redirect_to = wpforo_get_redirect_to();

	return $redirect_to ? '?redirect_to=' . urlencode( (string) $redirect_to ) : '';
}

function wpforo_login_url( $redirect_to = null ): string {
	if( wpforo_setting( 'authorization', 'login_url' ) ) {
		$login_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'login_url' ), '/' );
	} else {
		$login_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'login' );
	}
	$login_url = apply_filters( 'wpforo_login_url', $login_url );

	return esc_url_raw( $login_url );
}

function wpforo_register_url( $redirect_to = null ): string {
	if( wpforo_setting( 'authorization', 'register_url' ) ) {
		$register_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'register_url' ), '/' );
	} else {
		$register_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'register' );
	}
	$register_url = apply_filters( 'wpforo_register_url', $register_url );

	return esc_url_raw( $register_url );
}

function wpforo_logout_url( $redirect_to = null ): string {
	$logout_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'logout' );
	$logout_url = wp_nonce_url( $logout_url, 'wpforo_logout', '_wpfnonce' );
	$logout_url = apply_filters( 'wpforo_logout_url', $logout_url );

	return esc_url_raw( $logout_url );
}

function wpforo_lostpassword_url( $redirect_to = null ): string {
	if( wpforo_setting( 'authorization', 'lost_password_url' ) ) {
		$lostpassword_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'lost_password_url' ), '/' );
	} else {
		if( wpforo_setting( 'authorization', 'use_our_lostpassword_url' ) ) {
			$lostpassword_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'lostpassword' );
		} else {
			if( is_null( $redirect_to ) ) $redirect_to = wpforo_get_redirect_to();
			$lostpassword_url = wp_lostpassword_url( $redirect_to );
		}
	}
	$lostpassword_url = apply_filters( 'wpforo_lostpassword_url', $lostpassword_url );

	return esc_url_raw( $lostpassword_url );
}

function wpforo_resetpassword_url( $rp_key, $rp_login ): string {
	$rp_key            = rawurlencode( rawurldecode( $rp_key ) );
	$rp_login          = rawurlencode( rawurldecode( $rp_login ) );
	$resetpassword_url = wpforo_url( "?wpfaction=resetpassword_form&rp_key=$rp_key&rp_login=$rp_login", 'lostpassword' );
	$resetpassword_url = apply_filters( 'wpforo_resetpassword_url', $resetpassword_url );

	return esc_url_raw( $resetpassword_url );
}

function wpforo_members_url(): string {
	return wpforo_url( '', 'members' );
}

function wpforo_redirect_to() {
	$redirect_url = wpforo_home_url();
	if( ( $redirect_to = urldecode( (string) wpfval( $_GET, 'redirect_to' ) ) ) && wpforo_is_url_internal( $redirect_to ) && ( ! WPF()->current_userid || ( strpos(
		                                                                                                                                                        $redirect_to,
		                                                                                                                                                        wpforo_settings_get_slug( 'login' )
	                                                                                                                                                        ) === false && strpos(
		                                                                                                                                                                       $redirect_to,
		                                                                                                                                                                       wpforo_settings_get_slug(
			                                                                                                                                                                       'register'
		                                                                                                                                                                       )
	                                                                                                                                                                       ) === false && strpos(
		                                                                                                                                                                                      $redirect_to,
		                                                                                                                                                                                      wpforo_settings_get_slug(
			                                                                                                                                                                                      'lostpassword'
		                                                                                                                                                                                      )
	                                                                                                                                                                                      ) === false ) ) ) {
		$redirect_url = $redirect_to;
	}
	wp_safe_redirect( $redirect_url );
	exit();
}

function wpforo_settings_get_slug( $key ): string {
	$slug = wpfval( WPF()->settings->slugs, $key );
	if( ! $slug ) $slug = $key;

	return strtolower( (string) $slug );
}

function wpforo_settings_get_slug_key( $slug ) {
	$ret = array_search( $slug, WPF()->settings->slugs );
	if( $ret === false ) $ret = $slug;

	return $ret;
}

function wpforo_is_slug_base( $slug ): bool {
	return ! wpfkey( WPF()->tpl->templates, wpforo_settings_get_slug_key( $slug ) );
}

/**
 * @param string $group
 * @param string|int ...$name ... $_, ... [optional] more keys
 *
 * @return mixed|null
 */
function wpforo_setting( $group, $name ) {
	if( ! is_null( WPF()->settings ) && property_exists( WPF()->settings, $group ) ) {
		$args    = func_get_args();
		$args[0] = &WPF()->settings->$group;

		return call_user_func_array( 'wpfval', $args );
	}

	return null;
}

function _wpforo_apply_email_shortcodes( $txt, $args ) {
	$forumid          = (int) wpfval( $args, 'forumid' );
	$topicid          = wpforo_bigintval( wpfval( $args, 'topicid' ) );
	$postid           = wpforo_bigintval( wpfval( $args, 'postid' ) );
	$user             = (array) wpfval( $args, 'user' );
	$owner            = (array) wpfval( $args, 'owner' );
	$unsubscribe_link = (string) wpfval( $args, 'unsubscribe_link' );

	$txt = do_shortcode( $txt );

	return preg_replace_callback( '#\[[^\[\]]+?]#iu', function( $match ) use ( $forumid, $topicid, $postid, $user, $owner, $unsubscribe_link ) {
		$body_length = apply_filters( 'wpforo_email_notification_post_body_length', 1000 );
		$value       = '';
		$shortcode   = $match[0];

		if( $shortcode === '[unsubscribe_link]' ) {
			if( $unsubscribe_link ) {
				$value = sprintf( '<br><a target="_blank" href="%1$s">%2$s</a>', esc_url( (string) $unsubscribe_link ), wpforo_phrase( 'Unsubscribe', false ) );
				$value = stripslashes( $value );
			}
		} elseif( strpos( (string) $shortcode, '[user_' ) === 0 ) {
			if( wpforo_bigintval( wpfval( $user, 'userid' ) ) ) {
				$value = wpforo_user_field_shortcode_to_value( $shortcode, $user['userid'] );
				if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
					$value = '';
				}
			} else {
				if( $shortcode === '[user_display_name]' ) {
					$value = wpfval( $user, 'display_name' ) ?: wpforo_phrase( 'Anonymous', false );
				} elseif( $shortcode === '[user_user_email]' ) {
					$value = (string) wpfval( $user, 'user_email' );
				}
			}
		} elseif( strpos( (string) $shortcode, '[owner_' ) === 0 ) {
			if( wpforo_bigintval( wpfval( $owner, 'userid' ) ) ) {
				$value = wpforo_user_field_shortcode_to_value( $shortcode, $owner['userid'] );
				if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
					$value = '';
				}
			} else {
				if( $shortcode === '[owner_display_name]' ) {
					$value = wpfval( $owner, 'display_name' ) ?: wpforo_phrase( 'Anonymous', false );
				} elseif( $shortcode === '[owner_user_email]' ) {
					$value = (string) wpfval( $owner, 'user_email' );
				}
			}
		} elseif( strpos( (string) $shortcode, '[forum_' ) === 0 ) {
			if( $forumid ) {
				$value = wpforo_forum_field_shortcode_to_value( $shortcode, $forumid );
				if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
					$value = '';
				}
			}
		} elseif( strpos( (string) $shortcode, '[topic_' ) === 0 ) {
			if( $topicid ) {
				$value = wpforo_topic_field_shortcode_to_value( $shortcode, $topicid );
				if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
					$value = '';
				} elseif( $shortcode === '[topic_body]' ) {
					$value = wpforo_text( wpforo_kses( wpforo_content_filter( $value ), 'email' ), $body_length, false, false, false, true, false );
				}
			}
		} elseif( strpos( (string) $shortcode, '[post_' ) === 0 ) {
			if( $postid ) {
				$value = wpforo_post_field_shortcode_to_value( $shortcode, $postid );
				if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
					$value = '';
				} elseif( $shortcode === '[post_body]' ) {
					$value = wpforo_text( wpforo_kses( wpforo_content_filter( $value ), 'email' ), $body_length, false, false, false, true, false );
				}
			}
		}

		return $value;
	},                            (string) $txt );
}

function wpforo_apply_email_shortcodes( $txt, $pitem, $item, $owner, $user, $unsubscribe_link ) {
	$forumid = ( intval( wpfval( $pitem, 'forumid' ) ) ?: intval( wpfval( $item, 'forumid' ) ) );
	$topicid = ( wpforo_bigintval( wpfval( $pitem, 'topicid' ) ) ?: wpforo_bigintval( wpfval( $item, 'topicid' ) ) );
	$postid  = ( wpforo_bigintval( wpfval( $item, 'postid' ) ) ?: wpforo_bigintval( wpfval( $item, 'first_postid' ) ) );

	return _wpforo_apply_email_shortcodes( $txt, compact( 'forumid', 'topicid', 'postid', 'owner', 'user', 'unsubscribe_link' ) );
}

function wpforo_send_email( $email, $sbj, $msg, $headers = '', $context = 'general', $related_id = 0 ) {
	if( defined( 'IS_GO2WPFORO' ) && IS_GO2WPFORO ) return false;
	if( apply_filters( 'break_wpforo_send_email', false, $email, $sbj, $msg, $headers ) ) return false;
	$key = [ $email, $sbj, substr( $msg, 0, 100 ) ];
	if( WPF()->ram_cache->exists( $key ) ) return false;

	if( isset( WPF()->email_queue ) ) {
		$result = WPF()->email_queue->queue_or_send( $email, $sbj, $msg, $headers, $context, $related_id );
	} else {
		add_filter( 'wp_mail_content_type', 'wpforo_set_html_content_type', 999 );
		$result = wp_mail( $email, $sbj, $msg, ( $headers ?: wpforo_mail_headers() ) );
		remove_filter( 'wp_mail_content_type', 'wpforo_set_html_content_type' );
	}

	if( $result ) {
		WPF()->ram_cache->set( $key, true );
		return true;
	}

	return false;
}

function wpforo_get_site_default_locale() {
	$locale = trim( (string) get_option( 'WPLANG', 'en_US' ) );
	if( ! $locale ) $locale = 'en_US';

	return $locale;
}

function wpforo_sanitize( $action, $variable_name, $filter, $default = '' ) {
	if( $filter === "FILTER_SANITIZE_STRING" ) {
		$glob = INPUT_POST === $action ? $_POST : $_GET;
		if( wpfkey( $glob, $variable_name ) ) {
			return sanitize_text_field( $glob[ $variable_name ] );
		} else {
			return $default;
		}
	}
	$variable = isset( $variable_name ) ? filter_input( $action, $variable_name, $filter ) : '';

	return $variable ?: $default;
}

function wpforo_length( $str, $filtered = true ) {
	$str = $filtered ? strip_tags( trim( (string) $str ) ) : $str;
	if( function_exists( 'mb_substr' ) ) {
		$length = mb_strlen( (string) $str, get_option( 'blog_charset' ) );
	} else {
		$length = strlen( (string) $str );
	}

	return (int) $length;
}

if( ! function_exists( 'wpforo_remote_retrieve_header' ) ) {
	/**
	 *
	 * @param array|WP_Error $response HTTP response.
	 * @param string $header Header name to retrieve value from.
	 *
	 * @return string  string The header value. Empty string on if incorrect parameter given, or if the header doesn't exist.
	 */
	function wpforo_remote_retrieve_header( $response, $header ) {
		$header = wp_remote_retrieve_header( $response, $header );
		if( is_array( $header ) ) $header = (string) array_pop( $header );

		return $header;
	}
}

function wpforo_is_array_of_scalars( $array ) {
	if( ! is_array( $array ) ) return false;
	foreach( $array as $element ) if( ! is_scalar( $element ) ) return false;

	return true;
}

function wpforo_is_gzipped( $contents ): bool {
	// phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found
	if( function_exists( 'mb_strpos' ) ) {
		return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
	} else {
		return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
	}
	// phpcs:enable
}

function wpforo_sanitize_svg( $file ): bool {
	$dirty = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents

	// Is the SVG gzipped? If so we try and decode the string
	$is_zipped = wpforo_is_gzipped( $dirty );
	if( $is_zipped ) {
		$dirty = gzdecode( $dirty );

		// If decoding fails, bail as we're not secure
		if( false === $dirty ) {
			return false;
		}
	}

	$clean = trim( wpforo_kses( $dirty, 'svg' ) );

	if( ! $clean ) {
		return false;
	}

	// If we were gzipped, we need to re-zip
	if( $is_zipped ) {
		$clean = gzencode( $clean );
	}

	file_put_contents( $file, $clean ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents

	return true;
}

function wpforo_check_for_svg( $file ) {
	// Ensure we have a proper file path before processing
	if( ! isset( $file['tmp_name'] ) ) {
		return $file;
	}

	$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], ( $file['name'] ?? '' ), [] );
	$type        = ! empty( $wp_filetype['type'] ) ? $wp_filetype['type'] : (string) wpfval( $file, 'type' );

	if( in_array( $type, [ 'image/svg+xml', 'image/svg' ], true ) ) {
		if( ! wpforo_sanitize_svg( $file['tmp_name'] ) ) {
			$file['error'] = __(
				"Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded",
				'wpforo'
			);
		}
	}

	return $file;
}


/**
 * Custom function to replace is_numeric() function avoiding negative
 * numbers to be considered as numeric. This is mostly used differentiating
 * between ID and slugs. Sometimes slugs can be like "-12345".
 *
 * @param mixed $value
 *
 * @return bool
 */
function wpforo_is_id( $value ) {
	return is_numeric( $value ) && (int) $value >= 0;
}

function is_avatar_url( string $url ): bool {
	// Check if the string is a valid URL
	if( filter_var( $url, FILTER_VALIDATE_URL ) ) {
		// Make a HEAD request to check the Content-Type
		$response = wp_safe_remote_head( $url );

		if( ! is_wp_error( $response ) ) {
			$content_type = wp_remote_retrieve_header( $response, 'content-type' );

			// Check if the Content-Type is an image
			if( strpos( $content_type, 'image/' ) !== false ) {
				return true; // The URL points to an image
			}
		}
	}

	return false; // Not a valid avatar URL
}

/**
 * Function to generate image object for DiscussionForumPosting schema JSON-LD
 *
 * @param string $content
 *
 * @return string
 */
function wpforo_generate_scheme_image_object( $content ) {
	$image = '';
	if( function_exists( 'WPF_ATTACH' ) ) {
		$content = WPF_ATTACH()->tools->do_shortcodes( $content );
	}
	# TODO: Add support for secure URLs w/o image extensions
	# Currently, only URLs with image extensions are supported
	# by wpforo_find_image_urls() function.
	$images = wpforo_find_image_urls( $content, false );
	if( ! empty( $images ) ) {
		$image = '"image": [';
		foreach( $images as $img ) {
			$image .= '{ "@type": "ImageObject", "url": "' . esc_url_raw( $img ) . '"},';
		}
		$image = trim( $image, ',' ) . '],';
	}

	return $image;
}

function wpforo_generate_scheme_author_object( $user_id ) {
	$author       = '';
	$post_member  = wpforo_member( $user_id );
	$author_posts = wpfval( $post_member, 'posts' ) ? $post_member['posts'] : 1;
	$author_name  = wpfval( $post_member, 'display_name' ) ? $post_member['display_name'] : 'Guest';
	$author_url   = wpfval( $post_member, 'profile_url' ) ? '
	                    "url": "' . esc_url_raw( $post_member['profile_url'] ) . '",' : '';
	$author       = ',
                   "author": {
                        "@type": "Person",
                        "name": "' . esc_attr( $author_name ) . '",' . $author_url . '
                        "agentInteractionStatistic": {
                            "@type": "InteractionCounter",
                            "interactionType": "https://schema.org/WriteAction",
                            "userInteractionCount": ' . intval( $author_posts ) . '
                        }
                    }';

	return $author;
}

/**
 * Get wpForo home URL for a specific language/locale
 *
 * @param string $language Language code (e.g., 'de_DE', 'en_US', 'fr_FR')
 * @return string Forum home URL for the specified language
 */
function wpforo_get_home_url_for_language( $language ) {
    $sql = WPF()->db->prepare(
        "SELECT * FROM " . WPF()->tables->boards . " WHERE status = 1 AND locale = %s LIMIT 1",
        sanitize_locale_name($language)
    );
    $board = (array) WPF()->db->get_row( $sql, ARRAY_A );
	if( $board ) $board = WPF()->board->decode( $board );

    if( !$board ) {
        return home_url(); // Fallback to default
    }
    return trailingslashit( home_url( $board['slug'] ) );
}

function wpforo_get_current_language_url( $url = '' ) {
    $url = ( $url ) ? $url : '';
    if( function_exists( 'pll_current_language' ) && $language = pll_current_language( 'locale' ) ) {
        $sql = WPF()->db->prepare(
            "SELECT * FROM " . WPF()->tables->boards . " WHERE status = 1 AND locale = %s LIMIT 1",
            sanitize_locale_name($language)
        );
        $board = (array) WPF()->db->get_row( $sql, ARRAY_A );
        if( $boardid = wpfval($board, 'boardid') ){
            $url = ( $url ) ? $url : $_SERVER['REQUEST_URI'];
            $url = add_query_arg( 'boardid', intval( $boardid ), $_SERVER['REQUEST_URI'] );
        }
    }
    return $url;
}

/**
 * Centralized logging function for wpForo AI features.
 *
 * Nothing is logged unless wpforo General > Debug Mode is enabled
 * OR WordPress WP_DEBUG is on. This ensures zero disk usage on production sites.
 *
 * Log levels: 'error', 'info', 'debug'
 * - error: Logged when WP_DEBUG is on OR wpforo Debug Mode is on
 * - info:  Logged only when BOTH WP_DEBUG and wpforo Debug Mode are on
 * - debug: Logged when wpforo Debug Mode is on (with or without WP_DEBUG)
 *
 * Logs are written to: wp-content/uploads/wpforo/ai-logs/{Y-m-d}.log
 *
 * @param string $level   Log level: 'error', 'info', or 'debug'
 * @param string $message Log message
 * @param string $tag     Component tag, e.g. 'Moderation', 'Client', 'VectorStorage'
 */
function wpforo_ai_log( $level, $message, $tag = '' ) {
	static $enabled_levels = null;
	static $log_dir        = null;

	// Resolve enabled levels once per request
	if ( $enabled_levels === null ) {
		$enabled_levels = [];
		$wpforo_debug   = wpforo_setting( 'general', 'debug_mode' );
		$wp_debug       = defined( 'WP_DEBUG' ) && WP_DEBUG;
		if ( $wp_debug || $wpforo_debug ) {
			$enabled_levels[] = 'error';
		}
		if ( $wpforo_debug ) {
			$enabled_levels[] = 'debug';
		}
		if ( $wp_debug && $wpforo_debug ) {
			$enabled_levels[] = 'info';
		}
	}

	if ( ! in_array( $level, $enabled_levels, true ) ) {
		return;
	}

	// Resolve log directory once per request
	if ( $log_dir === null ) {
		$upload_dir = wp_upload_dir();
		$log_dir    = $upload_dir['basedir'] . '/wpforo/ai-logs';
		if ( ! is_dir( $log_dir ) ) {
			wp_mkdir_p( $log_dir );
			@file_put_contents( $log_dir . '/index.php', "<?php // Silence is golden.\n" );
		}
	}

	$prefix = '[wpForo AI' . ( $tag ? ' ' . $tag : '' ) . ']';
	$date   = gmdate( 'Y-m-d H:i:s' );
	$entry  = sprintf( "[%s] [%s] %s %s\n", $date, strtoupper( $level ), $prefix, $message );

	$log_file = $log_dir . '/' . gmdate( 'Y-m-d' ) . '.log';
	@file_put_contents( $log_file, $entry, FILE_APPEND | LOCK_EX );
}

/**
 * Get the master list of AI-supported languages.
 *
 * Single source of truth for all AI language lists across the plugin:
 * - Admin settings dropdowns (Settings.php)
 * - Frontend widget preference dropdown (header.php)
 * - Language name resolution (AIClient.php get_user_language)
 * - Preference validation whitelist (AIClient.php ajax_save_ai_preferences)
 *
 * Each entry: [ 'code' => '2-letter ISO code', 'locale' => 'WordPress locale', 'name' => 'English name', 'native' => 'Native name' ]
 *
 * @return array
 */
function wpforo_get_ai_languages() {
	return [
		// Major world languages
		[ 'code' => 'en', 'locale' => 'en_US', 'name' => 'English', 'native' => 'English' ],
		[ 'code' => 'en', 'locale' => 'en_GB', 'name' => 'English (UK)', 'native' => 'English (UK)' ],
		[ 'code' => 'zh', 'locale' => 'zh_CN', 'name' => 'Chinese Simplified', 'native' => '简体中文' ],
		[ 'code' => 'zh', 'locale' => 'zh_TW', 'name' => 'Chinese Traditional', 'native' => '繁體中文' ],
		[ 'code' => 'es', 'locale' => 'es_ES', 'name' => 'Spanish', 'native' => 'Español' ],
		[ 'code' => 'hi', 'locale' => 'hi_IN', 'name' => 'Hindi', 'native' => 'हिन्दी' ],
		[ 'code' => 'ar', 'locale' => 'ar', 'name' => 'Arabic', 'native' => 'العربية' ],
		[ 'code' => 'pt', 'locale' => 'pt_BR', 'name' => 'Portuguese (Brazil)', 'native' => 'Português Brasil' ],
		[ 'code' => 'pt', 'locale' => 'pt_PT', 'name' => 'Portuguese', 'native' => 'Português' ],
		[ 'code' => 'bn', 'locale' => 'bn_BD', 'name' => 'Bengali', 'native' => 'বাংলা' ],
		[ 'code' => 'ru', 'locale' => 'ru_RU', 'name' => 'Russian', 'native' => 'Русский' ],
		[ 'code' => 'ja', 'locale' => 'ja', 'name' => 'Japanese', 'native' => '日本語' ],
		[ 'code' => 'fr', 'locale' => 'fr_FR', 'name' => 'French', 'native' => 'Français' ],
		[ 'code' => 'de', 'locale' => 'de_DE', 'name' => 'German', 'native' => 'Deutsch' ],
		[ 'code' => 'ko', 'locale' => 'ko_KR', 'name' => 'Korean', 'native' => '한국어' ],
		[ 'code' => 'it', 'locale' => 'it_IT', 'name' => 'Italian', 'native' => 'Italiano' ],
		[ 'code' => 'tr', 'locale' => 'tr_TR', 'name' => 'Turkish', 'native' => 'Türkçe' ],
		[ 'code' => 'vi', 'locale' => 'vi_VN', 'name' => 'Vietnamese', 'native' => 'Tiếng Việt' ],
		[ 'code' => 'pl', 'locale' => 'pl_PL', 'name' => 'Polish', 'native' => 'Polski' ],
		[ 'code' => 'nl', 'locale' => 'nl_NL', 'name' => 'Dutch', 'native' => 'Nederlands' ],
		[ 'code' => 'th', 'locale' => 'th', 'name' => 'Thai', 'native' => 'ไทย' ],
		[ 'code' => 'id', 'locale' => 'id_ID', 'name' => 'Indonesian', 'native' => 'Bahasa Indonesia' ],
		[ 'code' => 'ms', 'locale' => 'ms_MY', 'name' => 'Malay', 'native' => 'Bahasa Melayu' ],
		// European languages
		[ 'code' => 'uk', 'locale' => 'uk', 'name' => 'Ukrainian', 'native' => 'Українська' ],
		[ 'code' => 'ro', 'locale' => 'ro_RO', 'name' => 'Romanian', 'native' => 'Română' ],
		[ 'code' => 'el', 'locale' => 'el_GR', 'name' => 'Greek', 'native' => 'Ελληνικά' ],
		[ 'code' => 'cs', 'locale' => 'cs_CZ', 'name' => 'Czech', 'native' => 'Čeština' ],
		[ 'code' => 'sv', 'locale' => 'sv_SE', 'name' => 'Swedish', 'native' => 'Svenska' ],
		[ 'code' => 'hu', 'locale' => 'hu_HU', 'name' => 'Hungarian', 'native' => 'Magyar' ],
		[ 'code' => 'da', 'locale' => 'da_DK', 'name' => 'Danish', 'native' => 'Dansk' ],
		[ 'code' => 'fi', 'locale' => 'fi', 'name' => 'Finnish', 'native' => 'Suomi' ],
		[ 'code' => 'no', 'locale' => 'no_NO', 'name' => 'Norwegian', 'native' => 'Norsk' ],
		[ 'code' => 'sk', 'locale' => 'sk_SK', 'name' => 'Slovak', 'native' => 'Slovenčina' ],
		[ 'code' => 'bg', 'locale' => 'bg_BG', 'name' => 'Bulgarian', 'native' => 'Български' ],
		[ 'code' => 'hr', 'locale' => 'hr', 'name' => 'Croatian', 'native' => 'Hrvatski' ],
		[ 'code' => 'sr', 'locale' => 'sr_RS', 'name' => 'Serbian', 'native' => 'Српски' ],
		[ 'code' => 'sl', 'locale' => 'sl_SI', 'name' => 'Slovenian', 'native' => 'Slovenščina' ],
		[ 'code' => 'lt', 'locale' => 'lt_LT', 'name' => 'Lithuanian', 'native' => 'Lietuvių' ],
		[ 'code' => 'lv', 'locale' => 'lv', 'name' => 'Latvian', 'native' => 'Latviešu' ],
		[ 'code' => 'et', 'locale' => 'et', 'name' => 'Estonian', 'native' => 'Eesti' ],
		[ 'code' => 'ca', 'locale' => 'ca', 'name' => 'Catalan', 'native' => 'Català' ],
		[ 'code' => 'gl', 'locale' => 'gl_ES', 'name' => 'Galician', 'native' => 'Galego' ],
		[ 'code' => 'eu', 'locale' => 'eu', 'name' => 'Basque', 'native' => 'Euskara' ],
		[ 'code' => 'sq', 'locale' => 'sq', 'name' => 'Albanian', 'native' => 'Shqip' ],
		[ 'code' => 'mk', 'locale' => 'mk_MK', 'name' => 'Macedonian', 'native' => 'Македонски' ],
		[ 'code' => 'bs', 'locale' => 'bs_BA', 'name' => 'Bosnian', 'native' => 'Bosanski' ],
		[ 'code' => 'mt', 'locale' => 'mt_MT', 'name' => 'Maltese', 'native' => 'Malti' ],
		[ 'code' => 'is', 'locale' => 'is_IS', 'name' => 'Icelandic', 'native' => 'Íslenska' ],
		[ 'code' => 'ga', 'locale' => 'ga', 'name' => 'Irish', 'native' => 'Gaeilge' ],
		[ 'code' => 'cy', 'locale' => 'cy', 'name' => 'Welsh', 'native' => 'Cymraeg' ],
		[ 'code' => 'be', 'locale' => 'be_BY', 'name' => 'Belarusian', 'native' => 'Беларуская' ],
		[ 'code' => 'lb', 'locale' => 'lb_LU', 'name' => 'Luxembourgish', 'native' => 'Lëtzebuergesch' ],
		// Middle Eastern & Central Asian
		[ 'code' => 'he', 'locale' => 'he_IL', 'name' => 'Hebrew', 'native' => 'עברית' ],
		[ 'code' => 'fa', 'locale' => 'fa_IR', 'name' => 'Persian', 'native' => 'فارسی' ],
		[ 'code' => 'ur', 'locale' => 'ur', 'name' => 'Urdu', 'native' => 'اردو' ],
		[ 'code' => 'ku', 'locale' => 'ku', 'name' => 'Kurdish', 'native' => 'Kurdî' ],
		[ 'code' => 'az', 'locale' => 'az', 'name' => 'Azerbaijani', 'native' => 'Azərbaycan' ],
		[ 'code' => 'ka', 'locale' => 'ka_GE', 'name' => 'Georgian', 'native' => 'ქართული' ],
		[ 'code' => 'hy', 'locale' => 'hy', 'name' => 'Armenian', 'native' => 'Հայերեն' ],
		[ 'code' => 'kk', 'locale' => 'kk', 'name' => 'Kazakh', 'native' => 'Қазақша' ],
		[ 'code' => 'uz', 'locale' => 'uz_UZ', 'name' => 'Uzbek', 'native' => 'Oʻzbek' ],
		[ 'code' => 'tg', 'locale' => 'tg', 'name' => 'Tajik', 'native' => 'Тоҷикӣ' ],
		[ 'code' => 'ky', 'locale' => 'ky_KY', 'name' => 'Kyrgyz', 'native' => 'Кыргызча' ],
		[ 'code' => 'tk', 'locale' => 'tk', 'name' => 'Turkmen', 'native' => 'Türkmen' ],
		[ 'code' => 'mn', 'locale' => 'mn', 'name' => 'Mongolian', 'native' => 'Монгол' ],
		// South Asian
		[ 'code' => 'ta', 'locale' => 'ta_IN', 'name' => 'Tamil', 'native' => 'தமிழ்' ],
		[ 'code' => 'te', 'locale' => 'te', 'name' => 'Telugu', 'native' => 'తెలుగు' ],
		[ 'code' => 'mr', 'locale' => 'mr', 'name' => 'Marathi', 'native' => 'मराठी' ],
		[ 'code' => 'gu', 'locale' => 'gu', 'name' => 'Gujarati', 'native' => 'ગુજરાતી' ],
		[ 'code' => 'kn', 'locale' => 'kn', 'name' => 'Kannada', 'native' => 'ಕನ್ನಡ' ],
		[ 'code' => 'ml', 'locale' => 'ml_IN', 'name' => 'Malayalam', 'native' => 'മലയാളം' ],
		[ 'code' => 'pa', 'locale' => 'pa_IN', 'name' => 'Punjabi', 'native' => 'ਪੰਜਾਬੀ' ],
		[ 'code' => 'si', 'locale' => 'si_LK', 'name' => 'Sinhala', 'native' => 'සිංහල' ],
		[ 'code' => 'ne', 'locale' => 'ne_NP', 'name' => 'Nepali', 'native' => 'नेपाली' ],
		[ 'code' => 'my', 'locale' => 'my_MM', 'name' => 'Burmese', 'native' => 'မြန်မာ' ],
		[ 'code' => 'km', 'locale' => 'km', 'name' => 'Khmer', 'native' => 'ខ្មែរ' ],
		[ 'code' => 'lo', 'locale' => 'lo', 'name' => 'Lao', 'native' => 'ລາວ' ],
		// East & Southeast Asian
		[ 'code' => 'tl', 'locale' => 'tl', 'name' => 'Filipino', 'native' => 'Filipino' ],
		[ 'code' => 'jv', 'locale' => 'jv_ID', 'name' => 'Javanese', 'native' => 'Basa Jawa' ],
		[ 'code' => 'su', 'locale' => 'su_ID', 'name' => 'Sundanese', 'native' => 'Basa Sunda' ],
		// African languages
		[ 'code' => 'sw', 'locale' => 'sw', 'name' => 'Swahili', 'native' => 'Kiswahili' ],
		[ 'code' => 'am', 'locale' => 'am', 'name' => 'Amharic', 'native' => 'አማርኛ' ],
		[ 'code' => 'ha', 'locale' => 'ha', 'name' => 'Hausa', 'native' => 'Hausa' ],
		[ 'code' => 'yo', 'locale' => 'yo', 'name' => 'Yoruba', 'native' => 'Yorùbá' ],
		[ 'code' => 'ig', 'locale' => 'ig_NG', 'name' => 'Igbo', 'native' => 'Igbo' ],
		[ 'code' => 'zu', 'locale' => 'zu', 'name' => 'Zulu', 'native' => 'isiZulu' ],
		[ 'code' => 'xh', 'locale' => 'xh', 'name' => 'Xhosa', 'native' => 'isiXhosa' ],
		[ 'code' => 'af', 'locale' => 'af', 'name' => 'Afrikaans', 'native' => 'Afrikaans' ],
		[ 'code' => 'so', 'locale' => 'so_SO', 'name' => 'Somali', 'native' => 'Soomaali' ],
		[ 'code' => 'rw', 'locale' => 'rw_RW', 'name' => 'Kinyarwanda', 'native' => 'Kinyarwanda' ],
		[ 'code' => 'mg', 'locale' => 'mg_MG', 'name' => 'Malagasy', 'native' => 'Malagasy' ],
		// Other
		[ 'code' => 'eo', 'locale' => 'eo', 'name' => 'Esperanto', 'native' => 'Esperanto' ],
		[ 'code' => 'la', 'locale' => 'la', 'name' => 'Latin', 'native' => 'Latina' ],
	];
}
