PluginProbe
wpForo Forum / 3.2.1
wpForo Forum v3.2.1
3.2.1 3.2.0 3.1.7 3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 All 141 releases
wpforo / includes / functions.php

functions.php in wpForo Forum 3.2.1, at includes/functions.php

4,432 lines 182.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Exit if accessed directly
3 if( ! defined( 'ABSPATH' ) ) exit;
4
5 function wpforo_get_modules_info( $base = null ): array {
6 $modules = [
7 'tags' => [
8 'base' => false,
9 'version' => '1.0.0',
10 'requires' => '1.5.2',
11 'class' => 'wpforo\classes\Topics',
12 'deps' => [],
13 'title' => 'Topic Tags',
14 '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>',
15 'desc' => __( 'Adds Tags Feature For Topics', 'wpforo' ),
16 'url' => 'https://wpforo.com',
17 ],
18 'subscriptions' => [
19 'base' => true,
20 'version' => '1.0.0',
21 'requires' => '1.0.2',
22 'class' => 'wpforo\modules\subscriptions\Subscriptions',
23 'deps' => [],
24 'title' => 'Subscriptions',
25 '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>',
26 'desc' => __( 'Adds Subscriptions & Mentioning Feature For Topics', 'wpforo' ),
27 'url' => 'https://wpforo.com',
28 ],
29 'mentioning' => [
30 'base' => true,
31 'version' => '1.0.0',
32 'requires' => '2.0.0',
33 'class' => 'wpforo\modules\mentioning\Mentioning',
34 'deps' => [ 'wpforo\modules\subscriptions\Subscriptions' ],
35 'title' => 'User Mentioning System',
36 '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>',
37 'desc' => __( 'Adds User Following System Feature For Users', 'wpforo' ),
38 'url' => 'https://wpforo.com',
39 ],
40 'follows' => [
41 'base' => true,
42 'version' => '1.0.0',
43 'requires' => '2.0.0',
44 'class' => 'wpforo\modules\follows\Follows',
45 'deps' => [ 'wpforo\modules\subscriptions\Subscriptions' ],
46 'title' => 'User Following System',
47 '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>',
48 'desc' => __( 'Adds User Following System Feature For Users', 'wpforo' ),
49 'url' => 'https://wpforo.com',
50 ],
51 'notifications' => [
52 'base' => false,
53 'version' => '1.0.0',
54 'requires' => '1.7.0',
55 'class' => 'wpforo\classes\Activity',
56 'deps' => [],
57 'title' => 'Forum Notifications',
58 '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>',
59 'desc' => __( 'Adds Forum Notifications Feature For Topics', 'wpforo' ),
60 'url' => 'https://wpforo.com',
61 ],
62 'logging' => [
63 'base' => false,
64 'version' => '1.0.0',
65 'requires' => '1.0.0',
66 'class' => 'wpforo\classes\Logs',
67 'deps' => [],
68 'title' => 'Action Logging & Views',
69 '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>',
70 'desc' => __( 'Adds Action Logging & Views Feature For Topics', 'wpforo' ),
71 'url' => 'https://wpforo.com',
72 ],
73 'seo' => [
74 'base' => false,
75 'version' => '1.0.0',
76 'requires' => '1.0.0',
77 'class' => 'wpforo\classes\SEO',
78 'deps' => [],
79 'title' => 'wpForo SEO',
80 '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>',
81 'desc' => __( 'Adds wpForo SEO Feature For Topics', 'wpforo' ),
82 'url' => 'https://wpforo.com',
83 ],
84 'antispam' => [
85 'base' => false,
86 'version' => '1.0.0',
87 'requires' => '1.0.0',
88 'class' => 'wpforo\classes\Moderation',
89 'deps' => [],
90 'title' => 'Spam Protection',
91 '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>',
92 'desc' => __( 'Adds Spam Protection Feature For Topics', 'wpforo' ),
93 'url' => 'https://wpforo.com',
94 ],
95 'akismet' => [
96 'base' => false,
97 'version' => '1.0.0',
98 'requires' => '1.0.0',
99 'class' => 'wpforo\classes\Moderation',
100 'deps' => [],
101 'title' => 'Akismet Antispam Integration',
102 'thumb' => WPFORO_URL . '/assets/images/dashboard/akismet.png',
103 'desc' => __( 'Adds Akismet Antispam Integration Feature For Topics', 'wpforo' ),
104 'url' => 'https://wpforo.com',
105 ],
106 'rss' => [
107 'base' => false,
108 'version' => '1.0.0',
109 'requires' => '1.0.0',
110 'class' => '',
111 'deps' => [],
112 'title' => 'Forum Feed / RSS',
113 '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>',
114 'desc' => __( 'Adds Forum Feed / RSS Feature For Topics', 'wpforo' ),
115 'url' => 'https://wpforo.com',
116 ],
117 'social' => [
118 'base' => false,
119 'version' => '1.0.0',
120 'requires' => '1.0.0',
121 'class' => 'wpforo\classes\API',
122 'deps' => [],
123 'title' => 'Social Share',
124 '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>',
125 'desc' => __( 'Adds Social Share Feature For Topics', 'wpforo' ),
126 'url' => 'https://wpforo.com',
127 ],
128 'revisions' => [
129 'base' => false,
130 'version' => '1.0.0',
131 'requires' => '1.0.0',
132 'class' => 'wpforo\modules\revisions\Revisions',
133 'deps' => [],
134 'title' => 'Post Preview and Auto Drafting',
135 '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>',
136 'desc' => __( 'Adds Post Preview and Auto Drafting Feature For Topics And Posts', 'wpforo' ),
137 'url' => 'https://wpforo.com',
138 ],
139 'bookmarks' => [
140 'base' => true,
141 'version' => '1.0.0',
142 'requires' => '2.0.0',
143 'class' => 'wpforo\modules\bookmarks\Bookmarks',
144 'deps' => [],
145 'title' => 'Post Bookmark',
146 '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>',
147 'desc' => __( 'Adds Post Bookmark Feature For Posts', 'wpforo' ),
148 'url' => 'https://wpforo.com',
149 ],
150 ];
151
152 if( ! is_null( $base ) ) {
153 $modules = array_filter( $modules, function( $module ) use ( $base ) {
154 return $module['base'] === $base;
155 } );
156 }
157
158 return $modules;
159 }
160
161 function wpforo_get_addons_info( $base = null ): array {
162 $addons = [
163 'wpforo-user-custom-fields' => [
164 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-user-custom-fields/wpforo-ucf.php' ),
165 'base' => true,
166 'version' => '3.0.0',
167 'requires' => '2.0.0',
168 'class' => 'WpforoUcf',
169 'deps' => [],
170 'title' => 'User Custom Fields',
171 'thumb' => WPFORO_URL . '/assets/addons/ucf/header.png',
172 'desc' => 'Advanced user profile builder system. Allows to add new fields and manage profile. Creates custom Registration, Account, Member Search forms.',
173 'url' => 'https://gvectors.com/product/wpforo-user-custom-fields/',
174 ],
175 'wpforo-topic-custom-fields' => [
176 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-topic-custom-fields/wpforotcf.php' ),
177 'base' => false,
178 'version' => '3.0.1',
179 'requires' => '2.0.0',
180 'class' => 'wpForoTcf',
181 'deps' => [],
182 'install_func' => function() {
183 if( class_exists( 'wpForoTcf' ) && function_exists( 'wpforotcf_activation' ) && version_compare( WPFORO_VERSION, WPFOROTCF_WPFORO_REQUIRED_VERSION, '>=' ) ) {
184 wpforotcf_activation();
185 }
186 },
187 'title' => 'Topic Custom Fields',
188 'thumb' => WPFORO_URL . '/assets/addons/tcf/header.png',
189 'desc' => 'Allows to create topic custom fields and manage topic form layout with a form builder. Adds topic search options by custom fields',
190 'url' => 'https://gvectors.com/product/wpforo-topic-custom-fields/',
191 ],
192 'wpforo-private-messages' => [
193 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-private-messages/wpforopm.php' ),
194 'base' => true,
195 'version' => '3.0.0',
196 'requires' => '2.0.0',
197 'class' => 'wpForoPMs',
198 'deps' => [],
199 'install_func' => function() {
200 if( class_exists( 'wpForoPMs' ) && function_exists( 'wpforopm_activation' ) && version_compare( WPFORO_VERSION, WPFOROPM_WPFORO_REQUIRED_VERSION, '>=' ) ) {
201 wpforopm_activation();
202 }
203 },
204 'title' => 'Private Messages',
205 'thumb' => WPFORO_URL . '/assets/addons/pm/header.png',
206 'desc' => 'Provides a safe way to communicate directly with other members. Messages are private and can only be viewed by conversation participants.',
207 'url' => 'https://gvectors.com/product/wpforo-private-messages/',
208 ],
209 'wpforo-topic-prefix' => [
210 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-topic-prefix/wpforotpx.php' ),
211 'base' => false,
212 'version' => '3.0.0',
213 'requires' => '2.0.0',
214 'class' => 'wpForoTopicPrefix',
215 'deps' => [],
216 'install_func' => function() {
217 if( class_exists( 'wpForoTopicPrefix' ) && function_exists( 'wpforotpx_activation' ) && version_compare( WPFORO_VERSION, WPFOROTPX_WPFORO_REQUIRED_VERSION, '>=' ) ) {
218 wpforotpx_activation();
219 }
220 },
221 'title' => 'Topic Prefix & Tag Manager',
222 'thumb' => WPFORO_URL . '/assets/addons/prefix/header.png',
223 '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.',
224 'url' => 'https://gvectors.com/product/wpforo-topic-prefix/',
225 ],
226 'wpforo-advanced-reactions' => [
227 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo_reactions/Main.php' ),
228 'base' => false,
229 'version' => '3.0.0',
230 'requires' => '2.3.2',
231 'class' => '\wpforo_reactions\Main',
232 'deps' => [],
233 'title' => 'Advanced Reactions',
234 'thumb' => WPFORO_URL . '/assets/addons/reactions/header.png',
235 'desc' => 'Allows users to add new types of reactions and provides a frontend dialog to view each post\'s currently reacted users',
236 'url' => 'https://gvectors.com/product/wpforo_reactions/',
237 ],
238 'wpforo-advanced-attachments' => [
239 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-advanced-attachments/wpforoattach.php' ),
240 'base' => false,
241 'version' => '3.0.1',
242 'requires' => '2.0.0',
243 'class' => 'wpForoAttachments',
244 'deps' => [],
245 'install_func' => function() {
246 if( class_exists( 'wpForoAttachments' ) && function_exists( 'wpforoattach_activation' ) && version_compare( WPFORO_VERSION, WPFOROATTACH_WPFORO_REQUIRED_VERSION, '>=' ) ) {
247 wpforoattach_activation();
248 }
249 },
250 'title' => 'Advanced Attachments',
251 'thumb' => WPFORO_URL . '/assets/addons/attachments/header.png',
252 'desc' => 'Adds an advanced file attachment system to forum. AJAX powered media uploading and displaying system with user specific library.',
253 'url' => 'https://gvectors.com/product/wpforo-advanced-attachments/',
254 ],
255 'wpforo-embeds' => [
256 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-embeds/wpforoembeds.php' ),
257 'base' => false,
258 'version' => '3.0.0',
259 'requires' => '2.0.0',
260 'class' => 'wpForoEmbeds',
261 'deps' => [],
262 'title' => 'Embeds',
263 'thumb' => WPFORO_URL . '/assets/addons/embeds/header.png',
264 'desc' => 'Allows to embed hundreds of video, social network, audio and photo content providers in forum topics and posts.',
265 'url' => 'https://gvectors.com/product/wpforo-embeds/',
266 ],
267 'wpforo-memberpress' => [
268 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-memberpress/wpforo_mempress.php' ),
269 'base' => true,
270 'version' => '1.0.0',
271 'requires' => '2.2.2',
272 'class' => 'wpForoMemberPress',
273 'deps' => [],
274 'install_func' => function() {
275 if( class_exists( 'wpForoMemberPress' ) && function_exists( 'wpforomp_activation' ) && version_compare( WPFORO_VERSION, WPFOROMP_WPFORO_REQUIRED_VERSION, '>=' ) ) {
276 wpforomp_activation();
277 }
278 },
279 'title' => 'MemberPress Integration',
280 'thumb' => WPFORO_URL . '/assets/addons/wpforo-memberpress/header.png',
281 'desc' => 'Allows to setup membership access to your forums through MemberPress membership plugin.',
282 'url' => 'https://gvectors.com/product/wpforo-memberpress/',
283 ],
284 'wpforo-groups' => [
285 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-groups-membership/wpforo_groups.php' ),
286 'base' => true,
287 'version' => '1.0.0',
288 'requires' => '2.2.2',
289 'class' => 'wpForoGroups',
290 'deps' => [],
291 'install_func' => function() {
292 if( class_exists( 'wpForoGroups' ) && function_exists( 'wpforomp_activation' ) && version_compare( WPFORO_VERSION, WPFOROGRP_WPFORO_REQUIRED_VERSION, '>=' ) ) {
293 wpforomp_activation();
294 }
295 },
296 'title' => 'Groups Plugin Integration',
297 'thumb' => WPFORO_URL . '/assets/addons/wpforo-groups/header.png',
298 'desc' => 'Allows to setup membership access to your forums through Groups plugin.',
299 'url' => 'https://gvectors.com/product/wpforo-groups-membership/',
300 ],
301 'wpforo-suremembers' => [
302 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-suremembers/wpforo_surmem.php' ),
303 'base' => true,
304 'version' => '1.0.0',
305 'requires' => '2.2.2',
306 'class' => 'wpForoSureMembers',
307 'deps' => [],
308 'install_func' => function() {
309 if( class_exists( 'wpForoSureMembers' ) && function_exists( 'wpforosm_activation' ) && version_compare( WPFORO_VERSION, WPFOROSM_WPFORO_REQUIRED_VERSION, '>=' ) ) {
310 wpforosm_activation();
311 }
312 },
313 'title' => 'SureMembers Integration',
314 'thumb' => WPFORO_URL . '/assets/addons/wpforo-suremembers/header.png',
315 'desc' => 'Allows to setup membership access to your forums through SureMembers membership plugin.',
316 'url' => 'https://gvectors.com/product/wpforo-suremembers/',
317 ],
318 'wpforo-paid-membership-pro' => [
319 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-paid-membership-pro/wpforo_mempro.php' ),
320 'base' => true,
321 'version' => '1.0.0',
322 'requires' => '2.2.2',
323 'class' => 'wpForoPaidMembershipPro',
324 'deps' => [],
325 'install_func' => function() {
326 if( class_exists( 'wpForoPaidMembershipPro' ) && function_exists( 'wpforopmp_activation' ) && version_compare( WPFORO_VERSION, WPFOROPMP_WPFORO_REQUIRED_VERSION, '>=' ) ) {
327 wpforopmp_activation();
328 }
329 },
330 'title' => 'Paid Membership Pro Integration',
331 'thumb' => WPFORO_URL . '/assets/addons/wpforo-paid-membership-pro/header.png',
332 'desc' => 'Allows to setup membership access to your forums through Paid Membership Pro plugin.',
333 'url' => 'https://gvectors.com/product/wpforo-paid-membership-pro/',
334 ],
335 'wpforo-woocommerce-memberships' => [
336 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-woocommerce-memberships/wpforo_woomem.php' ),
337 'base' => true,
338 'version' => '3.0.0',
339 'requires' => '2.0.0',
340 'class' => 'wpForoWooMembers',
341 'deps' => [],
342 'install_func' => function() {
343 if( class_exists( 'wpForoWooMembers' ) && function_exists( 'wpforowm_activation' ) && version_compare( WPFORO_VERSION, WPFOROWM_WPFORO_REQUIRED_VERSION, '>=' ) ) {
344 wpforowm_activation();
345 }
346 },
347 'title' => 'WooCommerce Memberships Integration',
348 'thumb' => WPFORO_URL . '/assets/addons/wpforo-woocomerce-memberships/header.png',
349 'desc' => 'Allows to setup membership access to your forums and topics through WooCommerce Memberships plugin.',
350 'url' => 'https://gvectors.com/product/wpforo-woocommerce-memberships/',
351 ],
352 'wpforo-mentioning' => [
353 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo_mentioning/Main.php' ),
354 'base' => false,
355 'version' => '3.0.0',
356 'requires' => '2.2.9',
357 'class' => '\wpforo_mentioning\Main',
358 'deps' => [],
359 'title' => 'User Mentioning',
360 'thumb' => WPFORO_URL . '/assets/addons/wpforo-mentioning/header.png',
361 'desc' => 'Opens pop-up window to select users for mentioning by @nickname.',
362 'url' => 'https://gvectors.com/product/wpforo_mentioning/',
363 ],
364 'wpforo-voice-posting' => [
365 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-voice-posting/wpforoVoicePosting.php' ),
366 'base' => false,
367 'version' => '3.0.0',
368 'requires' => '2.2.8',
369 'class' => 'wpforoVoicePosting',
370 'deps' => [],
371 'title' => 'Voice Posting',
372 'thumb' => WPFORO_URL . '/assets/addons/wpforo-voice-posting/header.png',
373 'desc' => 'Allows to record and attach voice messages to forum posts and private messages.',
374 'url' => 'https://gvectors.com/product/wpforo-voice-posting/',
375 ],
376 'wpforo-cross-posting' => [
377 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-cross-posting/wpForoCrossPosting.php' ),
378 'base' => false,
379 'version' => '3.0.0',
380 'requires' => '2.0.0',
381 'class' => 'wpForoCrossPosting',
382 'deps' => [],
383 'title' => '"Forum - Blog" Cross Posting',
384 'thumb' => WPFORO_URL . '/assets/addons/cross/header.png',
385 'desc' => 'Blog to Forum and Forum to Blog content synchronization. Blog posts with Forum topics and Blog comments with Forum replies.',
386 'url' => 'https://gvectors.com/product/wpforo-cross-posting/',
387 ],
388 'wpforo-ad-manager' => [
389 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-ad-manager/wpforoad.php' ),
390 'base' => false,
391 'version' => '3.0.1',
392 'requires' => '2.0.0',
393 'class' => 'wpForoAD',
394 'deps' => [],
395 'install_func' => function() {
396 if( class_exists( 'wpForoAD' ) && function_exists( 'wpforoad_activation' ) && version_compare( WPFORO_VERSION, WPFOROAD_WPFORO_REQUIRED_VERSION, '>=' ) ) {
397 wpforoad_activation();
398 }
399 },
400 'title' => 'Ads Manager',
401 'thumb' => WPFORO_URL . '/assets/addons/ad-manager/header.png',
402 'desc' => 'Ads Manager is a powerful yet simple advertisement management system. Allows you to add adverting banners between forums, topics and posts.',
403 'url' => 'https://gvectors.com/product/wpforo-ad-manager/',
404 ],
405 'wpforo-polls' => [
406 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-polls/wpforopoll.php' ),
407 'base' => false,
408 'version' => '3.0.0',
409 'requires' => '2.0.0',
410 'class' => 'wpForoPoll',
411 'deps' => [],
412 'install_func' => function() {
413 if( class_exists( 'wpForoPoll' ) && function_exists( 'wpforopoll_activation' ) && version_compare( WPFORO_VERSION, WPFOROPOLL_WPFORO_REQUIRED_VERSION, '>=' ) ) {
414 wpforopoll_activation();
415 }
416 },
417 'title' => 'Polls',
418 'thumb' => WPFORO_URL . '/assets/addons/polls/header.png',
419 'desc' => 'wpForo Polls is a complete addon to help forum members create, vote and manage polls effectively. Comes with poll specific permissions and settings.',
420 'url' => 'https://gvectors.com/product/wpforo-polls/',
421 ],
422 'wpforo-emoticons' => [
423 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-emoticons/wpforosmile.php' ),
424 'base' => false,
425 'version' => '3.0.0',
426 'requires' => '2.0.0',
427 'class' => 'wpForoSmiles',
428 'deps' => [],
429 'install_func' => function() {
430 if( class_exists( 'wpForoSmiles' ) && function_exists( 'wpforosmile_activation' ) && version_compare( WPFORO_VERSION, WPFOROSMILE_WPFORO_REQUIRED_VERSION, '>=' ) ) {
431 wpforosmile_activation();
432 }
433 },
434 'title' => 'wpForo Emoticons',
435 'thumb' => WPFORO_URL . '/assets/addons/wpforo-emoticons/header.png',
436 'desc' => 'Adds awesome Sticker and Emoticons packs to editor. Allows to create new custom emoticons packs.',
437 'url' => 'https://gvectors.com/product/wpforo-emoticons/',
438 ],
439 'wpforo-tenor' => [
440 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-tenor/wpforotenor.php' ),
441 'base' => false,
442 'version' => '3.0.0',
443 'requires' => '2.0.0',
444 'class' => 'wpForoTenor',
445 'deps' => [],
446 'title' => 'Tenor GIFs Integration',
447 'thumb' => WPFORO_URL . '/assets/addons/tenor/header.png',
448 'desc' => 'Adds Tenor [GIF] button and opens popup where you can search for gifs and insert them in topic, post and private message content.',
449 'url' => 'https://gvectors.com/product/wpforo-tenor/',
450 ],
451 'wpforo-giphy' => [
452 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-giphy/wpforogiphy.php' ),
453 'base' => false,
454 'version' => '3.0.0',
455 'requires' => '2.0.0',
456 'class' => 'wpForoGiphy',
457 'deps' => [],
458 'title' => 'GIPHY Integration',
459 'thumb' => WPFORO_URL . '/assets/addons/giphy/header.png',
460 'desc' => 'Adds GIPHY [GIF] button and opens popup where you can search for gifs and insert them in topic, post and private message content.',
461 'url' => 'https://gvectors.com/product/wpforo-giphy/',
462 ],
463 'wpforo-mycred' => [
464 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-mycred/wpforo-mc.php' ),
465 'base' => true,
466 'version' => '3.0.0',
467 'requires' => '2.0.0',
468 'class' => 'myCRED_Hook_wpForo',
469 'deps' => [],
470 'title' => 'MyCRED Integration',
471 'thumb' => WPFORO_URL . '/assets/addons/mycred/header.png',
472 'desc' => 'Awards myCRED points for forum activity. Integrates myCRED Badges and Ranks. Converts wpForo topic and posts, likes to myCRED points.',
473 'url' => 'https://gvectors.com/product/wpforo-mycred/',
474 ],
475 'wpforo-syntax-highlighter' => [
476 'ABSPATH' => wpforo_fix_dir_sep( WP_PLUGIN_DIR . '/wpforo-syntax-highlighter/wpForoSyntaxHighlighter.php' ),
477 'base' => false,
478 'version' => '3.0.0',
479 'requires' => '2.0.0',
480 'class' => 'wpForoSyntaxHighlighter',
481 'deps' => [],
482 'install_func' => function() {
483 if( class_exists( 'wpForoSyntaxHighlighter' ) && function_exists( 'wpForoSyntaxActivation' ) && version_compare( WPFORO_VERSION, WPFOROSYNTAX_WPFORO_REQUIRED_VERSION, '>=' ) ) {
484 wpForoSyntaxActivation();
485 }
486 },
487 'title' => 'Syntax Highlighter',
488 'thumb' => WPFORO_URL . '/assets/addons/syntax/header.png',
489 'desc' => 'Syntax highlighting for forum posts, automatic language detection and multi-language code highlighting.',
490 'url' => 'https://gvectors.com/product/wpforo-syntax-highlighter/',
491 ],
492
493 ];
494
495 if( ! is_null( $base ) ) {
496 $addons = array_filter( $addons, function( $addon ) use ( $base ) {
497 return $addon['base'] === $base;
498 } );
499 }
500
501 return $addons;
502 }
503
504 function wpforo_get_ajax_actions_list() {
505 $nonces = [];
506 global $wp_filter;
507 if( WPF()->current_userid ) {
508 foreach( $wp_filter as $key => $value ) {
509 if( strpos( (string) $key, 'wp_ajax_wpf' ) === 0 ) $nonces[] = preg_replace( '#^wp_ajax_#iu', '', (string) $key, 1 );
510 }
511 } else {
512 foreach( $wp_filter as $key => $value ) {
513 if( strpos( (string) $key, 'wp_ajax_nopriv_wpf' ) === 0 ) $nonces[] = preg_replace( '#^wp_ajax_nopriv_#iu', '', (string) $key, 1 );
514 }
515 }
516
517 return array_unique( (array) apply_filters( 'wpforo_get_ajax_actions_list', $nonces ) );
518 }
519
520 function wpforo_generate_ajax_nonces() {
521 $nonces = [];
522 foreach( wpforo_get_ajax_actions_list() as $action ) {
523 $nonces[ $action ] = wp_create_nonce( $action );
524 }
525
526 return $nonces;
527 }
528
529 function wpforo_verify_form( $action = 'wpforo_verify_form', $query_arg = '_wpfnonce', $mode = 'full' ) {
530 wpforo_verify_nonce( $action, $mode, $query_arg );
531 do_action( 'wpforo_verify_form_end' );
532 }
533
534 function wpforo_verify_nonce( $action = 'wpforo_verify_form', $mode = 'ajax', $query_arg = '_wpfnonce' ) {
535 if( $mode === 'full' && apply_filters( 'wpforo_check_request', true ) ) {
536 if( ! wp_verify_nonce( wpfval( $_REQUEST, $query_arg ), $action ) ) {
537 wpforo_phrase( 'Sorry, something is wrong with your data.' );
538 exit();
539 }
540 } elseif( $mode === 'ajax' ) {
541 check_ajax_referer( $action, $query_arg );
542 }
543 if( apply_filters( 'wpforo_check_referer', true ) && in_array( $mode, [ 'ajax', 'full', 'ref' ] ) ) {
544 if( ! wpfval( $_SERVER, 'HTTP_REFERER' ) ) {
545 exit( 'Error 2252 | Please contact the forum administrator.' );
546 }
547 if( wpforo_is_url_external( $_SERVER['HTTP_REFERER'] ) ) {
548 exit( 'Error 2253 | Please contact the forum administrator.' );
549 }
550 }
551 do_action( 'wpforo_verify_nonce_end' );
552 }
553
554 /**
555 * @param string $path
556 * @param string|null $route
557 * @param string|null $scheme
558 *
559 * @return string
560 */
561 function wpforo_url( $path = '', $route = null, $scheme = null ) {
562 if( $route ) $route = wpforo_settings_get_slug( $route );
563 if( is_null( $route ) ) $route = WPF()->board->full_route;
564 $path = trim( trim( (string) $route, '/\\' ) . '/' . trim( (string) $path, '/\\' ), '/\\' );
565
566 if( function_exists( 'pll_default_language' ) && function_exists( 'PLL' ) ) {
567 if( ! PLL()->options['hide_default'] && $lng = pll_default_language() ) $path = $lng . '/' . $path;
568 }
569
570 $url = home_url( $path, $scheme );
571 //-START- check is url maybe WordPress home
572 $maybe_home_url = trim( preg_replace( '#/?index\.php/?(\?.*)?$#isu', '', (string) $url ), '/\\' );
573 $home_url = trim( home_url( '', $scheme ), '/\\' );
574 if( $maybe_home_url === $home_url ) $url = preg_replace( '#index\.php/?#isu', '', (string) $url, 1 );
575
576 //-END- check is url maybe WordPress home
577
578 $url = apply_filters( 'wpforo_url', $url, $path, $route, $scheme );
579
580 return WPF()->user_trailingslashit( $url );
581 }
582
583 /**
584 * @param string $path
585 * @param string|null $scheme
586 *
587 * @return string
588 */
589 function wpforo_home_url( $path = '', $scheme = null ) {
590 if( strpos( (string) $path, 'http' ) === 0 ) {
591 $path = wpforo_get_url_query_vars_str( $path );
592 $path = preg_replace( '#index\.php/?#isu', '', (string) $path, 1 );
593 $path = preg_replace( '#^/?' . preg_quote( trim( (string) WPF()->board->route, '/\\' ) ) . '#isu', '', (string) $path, 1 );
594 }
595
596 return wpforo_url( $path, null, $scheme );
597 }
598
599 function wpforo_is_ajax() {
600 return defined( 'DOING_AJAX' ) && DOING_AJAX;
601 }
602
603 function wpforo_is_admin( $url = '' ) {
604 $url = trim( (string) $url );
605 if( $url ) return strpos( (string) $url, trim( admin_url(), '/' ) ) === 0 || strpos( $url, trim( self_admin_url(), '/' ) ) === 0;
606
607 return is_admin() && ! wpforo_is_ajax();
608 }
609
610 function _is_wpforo_page( $url = '' ) {
611 if( ! $url ) $url = wpforo_get_request_uri();
612 $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 ) ) );
613
614 return apply_filters( 'is_wpforo_page', $result, $url );
615 }
616
617 function is_wpforo_page( $url = '' ) {
618 return wpforo_ram_get( '_is_wpforo_page', $url );
619 }
620
621 function _is_wpforo_exclude_url( $url = '' ) {
622 $result = false;
623 if( ! $url ) $url = wpforo_get_request_uri();
624 $url = urldecode( (string) $url );
625 $url = preg_replace( '#/page/\d*/?$#iu', '', (string) $url );
626 if( $current_url = wpforo_get_url_query_vars_str( $url ) ) {
627 if( preg_match( '#^/?(?:([^\s/?&=<>:\'\"*\\\|]*/)(?1)*)?[^\s/?&=<>:\'\"*\\\|]+\.(?:php|js|css|jpe?g|png|gif|webp|svg|bmp|tiff)/?(?:\?[^/]*)?$#iu', $current_url ) ) {
628 $result = true;
629 } elseif( WPF()->board->get_current( 'is_standalone' ) && ( $excld_urls = WPF()->board->get_current( 'excld_urls' ) ) ) {
630 foreach( $excld_urls as $excld_url ) {
631 $excld_url = urldecode( (string) $excld_url );
632 if( wpforo_is_url_internal( $excld_url ) ) {
633 $excld_url = wpforo_get_url_query_vars_str( $excld_url );
634 $pattern = preg_quote( $excld_url );
635 $pattern = str_replace( [ '/\*', '\*' ], [ '/?[^\r\n\t\s\0]*?', '[^\r\n\t\s\0]*?' ], $pattern );
636 if( preg_match( '#^' . $pattern . '$#iu', $current_url ) ) {
637 $result = true;
638 break;
639 }
640 }
641 }
642 }
643 }
644
645 return $result;
646 }
647
648 function is_wpforo_exclude_url( $url = '' ) {
649 return wpforo_ram_get( '_is_wpforo_exclude_url', $url );
650 }
651
652 function _is_wpforo_url( $url = '' ) {
653 $result = false;
654 if( ! wpforo_is_admin( $url ) ) {
655 if( WPF()->board->get_current( 'is_standalone' ) && ! is_wpforo_exclude_url( $url ) ) {
656 $result = true;
657 } else {
658 $current_url = wpforo_get_url_query_vars_str( $url );
659 $current_url = preg_replace( '#/?\?.*$#isu', '', $current_url );
660 foreach( WPF()->board->routes as $route ) {
661 if( $route && ( $current_url === $route || strpos( (string) $current_url, $route . '/' ) === 0 ) ) {
662 $result = true;
663 break;
664 }
665 }
666 }
667 }
668
669 return $result;
670 }
671
672 function is_wpforo_url( $url = '' ) {
673 return wpforo_ram_get( '_is_wpforo_url', $url );
674 }
675
676 /**
677 * @param string $url
678 *
679 * @return bool
680 */
681 function _is_wpforo_shortcode_page( $url = '' ) {
682 $result = ! wpforo_is_admin( $url ) && ! is_wpforo_url( $url ) && has_shortcode( wpforo_get_wp_post_content( $url ), 'wpforo' );
683
684 return apply_filters( 'wpforo_is_shortcode_page', $result, $url );
685 }
686
687 function is_wpforo_shortcode_page( $url = '' ) {
688 return wpforo_ram_get( '_is_wpforo_shortcode_page', $url );
689 }
690
691 function _wpforo_get_wp_post_content( $url = '' ) {
692 if( ! $url ) $url = wpforo_get_request_uri();
693 $post_content = '';
694 global $post;
695 if( $url === wpforo_get_request_uri() && is_a( $post, 'WP_Post' ) ) {
696 $post_content = $post->post_content;
697 } elseif( $postid = wpforo_wp_url_to_postid( $url ) ) {
698 $post_content = get_post_field( 'post_content', $postid );
699 }
700
701 return $post_content;
702 }
703
704 function wpforo_get_wp_post_content( $url = '' ) {
705 return wpforo_ram_get( '_wpforo_get_wp_post_content', $url );
706 }
707
708
709 /**
710 * @param string $text
711 * @param string $url
712 *
713 * @return array|string
714 */
715 function get_wpforo_shortcode_atts( $text = '', $url = '' ) {
716 if( ! $text ) $text = wpforo_get_wp_post_content( $url );
717 if( preg_match( '#\[[\r\n\t\s\0]*wpforo[\r\n\t\s\0]*([^\[\]]*?)]#iu', $text, $match ) ) {
718 return shortcode_parse_atts( $match[1] );
719 }
720
721 return '';
722 }
723
724 function wpforo_get_url_query_vars_str( $url = '' ) {
725 if( ! $url ) $url = wpforo_get_request_uri();
726 $home_url = preg_replace( '#/?\?.*$#isu', '', home_url( '/' ) );
727 $current_url = preg_replace( '#https?://[^/\?]+/?#isu', '', $url );
728 $site_url = preg_replace( '#https?://[^/\?]+/?#isu', '', $home_url );
729 if( strpos( (string) $current_url, '/' ) === false && $current_url === trim( (string) $site_url, '/' ) ) return '';
730 $current_url = preg_replace( '#^/?' . preg_quote( $site_url ) . '(?:/?index\.php/?)?#isu', '', $current_url, 1 );
731 $current_url = preg_replace( '#^/?' . preg_quote( wpforo_get_query_var_lang() ) . '/#isu', '', $current_url, 1 );
732 $current_url = preg_replace( '#^[\s\0/]*(.*?)[\s\0/]*$#isu', '$1', $current_url );
733
734 return wpforo_fix_url( $current_url );
735 }
736
737 function wpforo_get_url_route( $url = '' ) {
738 $route = '';
739 $query_vars_str = preg_replace( '#/?\?.*$#isu', '', wpforo_get_url_query_vars_str( (string) $url ) );
740 $query_vars = array_values( array_filter( explode( '/', trim( (string) $query_vars_str, '/' ) ) ) );
741 if( array_key_exists( 0, $query_vars ) && in_array( $query_vars[0], WPF()->board->routes ) ) $route = $query_vars[0];
742
743 return $route;
744 }
745
746 function wpforo_dir_size( $directory ) {
747 $size = 0;
748 if( is_dir( $directory ) && class_exists( 'RecursiveIteratorIterator' ) && class_exists( 'RecursiveDirectoryIterator' ) ) {
749 foreach( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $directory ) ) as $file ) $size += $file->getSize();
750 }
751
752 return $size;
753 }
754
755 function wpforo_make_hidden_fields_from_url( $url = '', $echo = true ) {
756 if( ! $url ) $url = wpforo_get_request_uri();
757 $return = '';
758 if( $url_query = parse_url( $url, PHP_URL_QUERY ) ) {
759 parse_str( $url_query, $url_query_arr );
760 if( ! empty( $url_query_arr ) ) {
761 foreach( $url_query_arr as $key => $value ) {
762 $return .= '<input type="hidden" name="' . $key . '" value="' . $value . '">';
763 }
764 }
765 }
766 if( ! $echo ) return $return;
767 echo $return;
768 }
769
770 /**
771 * Returns merged arguments array from defined and default arguments.
772 *
773 * @param mixed $args
774 * @param array $default
775 *
776 * @return array
777 */
778 function wpforo_parse_args( $args, $default = [] ) {
779 if( is_object( $args ) ) {
780 $defined = get_object_vars( $args );
781 } elseif( is_array( $args ) ) {
782 $defined = $args;
783 } elseif( preg_match( '|^[\d,\s]+$|', (string) $args ) ) {
784 $defined = explode( ',', trim( (string) $args ) );
785 } elseif( is_integer( $args ) || is_float( $args ) ) {
786 $defined[0] = $args;
787 } elseif( is_serialized( $args ) ) {
788 // SECURITY: refuse to instantiate any class — only scalars and arrays
789 // of scalars are recovered. Blocks PHP Object Injection via widget
790 // AJAX (forumids etc.) and any other caller that may receive
791 // attacker-controlled strings.
792 $defined = unserialize( $args, [ 'allowed_classes' => false ] );
793 if( ! is_array( $defined ) ) $defined = (array) $defined;
794 } elseif( strpos( (string) $args, '=' ) !== false ) {
795 parse_str( $args, $defined );
796 } else {
797 $defined = (array) $args;
798 }
799 if( ! empty( $default ) ) {
800 return array_merge( $default, $defined );
801 } else {
802 return $defined;
803 }
804 }
805
806 /**
807 * Detects serialized data
808 *
809 * @param string
810 *
811 * @return boolean
812 * @since 1.0.0
813 *
814 */
815 if( ! function_exists( 'is_serialized' ) ) {
816 function is_serialized( $value ) {
817 if( $value == '' ) return false;
818 $value = trim( (string) $value );
819 // SECURITY: the very act of detecting a serialized payload must not
820 // instantiate classes — @unserialize() with the default options runs
821 // __wakeup() / __destruct() for any embedded object even when the
822 // caller only wanted to test the format. Limit to scalars/arrays.
823 $chsd = @unserialize( $value, [ 'allowed_classes' => false ] );
824 if( $chsd !== false || $value === 'b:0;' ) {
825 return true;
826 } else {
827 return false;
828 }
829 }
830 }
831
832 function wpforo_get_request_uri( $with_port = false, $get_referer_when_ajax = true ) {
833 if( $get_referer_when_ajax && wpforo_is_ajax() ) {
834 if( $referer = wpfval( $_REQUEST, 'referer' ) ) {
835 $referer = preg_replace( '#\#[^/?&]*$#iu', '', (string) $referer );
836
837 return esc_url_raw( $referer );
838 }
839 if( $referer = wpfval( $_SERVER, 'HTTP_REFERER' ) ) {
840 $url = preg_replace( '#\#[^/?&]*$#iu', '', (string) $referer );
841
842 return esc_url_raw( $url );
843 }
844 }
845 $s = is_ssl() ? 's' : '';
846 $sp = strtolower( (string) wpfval( $_SERVER, 'SERVER_PROTOCOL' ) );
847 $protocol = substr( $sp, 0, strpos( (string) $sp, "/" ) ) . $s;
848 $server_port = intval( wpfval( $_SERVER, 'SERVER_PORT' ) );
849 $port = $server_port === 80 ? "" : ":" . $server_port;
850 $host = preg_replace( '#^www\.#iu', '', (string) wpfval( $_SERVER, 'HTTP_HOST' ) );
851 if( strpos( home_url(), 'www.' ) !== false ) $host = 'www.' . $host;
852 $requri = wpfval( $_SERVER, 'UNENCODED_URL' ) ?: wpfval( $_SERVER, 'REQUEST_URI' );
853 $url = $protocol . "://" . $host . ( $with_port && $server_port ? $port : '' ) . $requri;
854 $url = wpforo_fix_url( $url );
855
856 return esc_url_raw( $url );
857 }
858
859 function wpforo_arr_group_by( $array, $key_by ) {
860 if( ! empty( $array ) ) {
861 $fltrd = [];
862 foreach( $array as $key => $arr ) {
863 if( is_numeric( $key ) ) $fltrd[] = $arr[ $key_by ];
864 }
865 $uniq_arr = array_unique( $fltrd );
866 asort( $uniq_arr );
867
868 return $uniq_arr;
869 }
870 }
871
872 function wpforo_phrase( $key, $echo = true, $format = 'first-upper' ) {
873 static $cache = [];
874
875 $locale = WPF()->locale;
876 $cache_key = $key . '|' . $locale . '|' . $format;
877
878 // For English locale, we can safely cache (no domain/backtrace issues)
879 if ( 'en_US' === $locale && isset( $cache[ $cache_key ] ) ) {
880 if ( $echo ) {
881 echo $cache[ $cache_key ];
882 }
883 return $cache[ $cache_key ];
884 }
885
886 // Normalize key once for phrase lookup
887 $normalized_key = addslashes( strtolower( (string) $key ) );
888 $phrase = isset( WPF()->phrase->phrases[ $normalized_key ] ) ? WPF()->phrase->phrases[ $normalized_key ] : $key;
889
890 if( 'en_US' !== $locale ) {
891 $native = $phrase;
892 $backtrace = wp_debug_backtrace_summary();
893 $mopo_domain = 'wpforo';
894 if( strpos( (string) $backtrace, '\plugins\wpforo-private-messages\\' ) !== false || strpos( (string) $backtrace, '/plugins/wpforo-private-messages/' ) !== false ) $mopo_domain = 'wpforo_pm';
895 if( strpos( (string) $backtrace, 'wpForoPolls' ) !== false ) $mopo_domain = 'wpforo_poll';
896 $key = preg_replace( "/(^\s+)|(\s+$)/u", "", (string) $key );
897 $phrase = preg_replace( "/(^\s+)|(\s+$)/u", "", (string) $phrase );
898 if( strtolower( (string) $key ) === strtolower( (string) $phrase ) ) {
899 $phrase = __( $key, $mopo_domain );
900 if( strtolower( (string) $key ) === strtolower( (string) $phrase ) ) {
901 $key = strtolower( (string) $key );
902 $phrase = __( $key, $mopo_domain );
903 if( strtolower( (string) $key ) === strtolower( (string) $phrase ) ) {
904 $phrase = __( ucfirst( (string) $key ), $mopo_domain );
905 if( strtolower( (string) $key ) === strtolower( (string) $phrase ) ) {
906 $phrase = __( $native, $mopo_domain ); //Try all, if no result pass the original text to translation again.
907 }
908 }
909 }
910 }
911 }
912
913 if( $format === 'first-upper' ) {
914 if( 'en_US' !== $locale && function_exists( 'mb_strlen' ) && mb_strlen( (string) $phrase ) !== strlen( (string) $phrase ) && function_exists( 'mb_strtoupper' ) ) {
915 $phrase = mb_strtoupper( mb_substr( (string) $phrase, 0, 1 ) ) . mb_substr( (string) $phrase, 1 );
916 } else {
917 $phrase = ucfirst( (string) $phrase );
918 }
919 } elseif( $format === 'upper' ) {
920 if( function_exists( 'mb_strtoupper' ) ) {
921 $phrase = mb_strtoupper( (string) $phrase );
922 } else {
923 $phrase = strtoupper( (string) $phrase );
924 }
925 } elseif( $format === 'lower' ) {
926 if( function_exists( 'mb_strtolower' ) ) {
927 $phrase = mb_strtolower( (string) $phrase );
928 } else {
929 $phrase = strtolower( (string) $phrase );
930 }
931 }
932
933 $phrase = str_replace( '{number}', '', $phrase );
934
935 // Cache for English locale (safe - no domain dependency)
936 if ( 'en_US' === $locale ) {
937 $cache[ $cache_key ] = $phrase;
938 }
939
940 if( $echo ) echo $phrase;
941
942 return $phrase;
943 }
944
945 function wpforo_screen_option() { ?>
946 <div id="screen-meta" class="metabox-prefs" style="display: none; ">
947 <div id="screen-options-wrap" class="hidden" tabindex="-1" aria-label="Screen Options Tab" style="display: none; ">
948 <form id="adv-settings" action="" method="POST">
949 <input type="hidden" name="wpfaction" value="dashboard_options_save">
950 <h5><?php _e( 'Show on screen', 'wpforo' ) ?></h5>
951
952 <div class="screen-options">
953 <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"
954 value="<?php echo wpforo_get_option( 'count_per_page', 10 ) ?>">
955 <label for="edit_post_per_page"><?php _e( 'Items', 'wpforo' ) ?></label>
956 <input type="submit" id="screen-options-apply" class="button" value="<?php _e( 'Apply', 'wpforo' ) ?>">
957 </div>
958 </form>
959 </div>
960 </div>
961
962 <div id="screen-meta-links">
963 <div id="screen-options-link-wrap" class="hide-if-no-js screen-meta-toggle" style="">
964 <a href="#screen-options-wrap" id="show-settings-link" class="show-settings screen-meta-active" aria-controls="screen-options-wrap" aria-expanded="true">
965 <?php _e( 'Screen Options', 'wpforo' ) ?>
966 </a>
967 </div>
968 </div>
969
970 <?php
971 }
972
973 function wpforo_strip_shortcodes( $text, $only_wpforo_shortcodes = false ) {
974 $text = (string) apply_filters( 'wpforo_strip_shortcodes', (string) $text, $only_wpforo_shortcodes );
975 $text = preg_replace( '#\[attach[^\[\]]*][^\[\]]*\[/attach]#iu', '', $text );
976 $text = preg_replace( '#\[spoiler[^\[\]]*][^\[\]]*\[/spoiler]#iu', '', $text );
977 $text = preg_replace( '#\[quote[^\[\]]*][^\[\]]*\[/quote]#iu', '', $text );
978 $text = preg_replace( '#\[wpfgiphy[^\[\]]+?]#iu', '', $text );
979 $text = preg_replace( '#\[wpftenor[^\[\]]+?]#iu', '', $text );
980 // $text = preg_replace('#(?:{\w+})?:[^/.\s]+:#iu', '', $text);
981 if( ! $only_wpforo_shortcodes ) $text = strip_shortcodes( $text );
982
983 return $text;
984 }
985
986 function wpforo_strip_quotes( $text ) {
987 $text = preg_replace( '#<(blockquote)[^<>]*?data-userid[^<>]*?>(?:.*?(?R)*.*?)*?</\1>#isu', '', (string) $text );
988
989 return preg_replace( '#\[(quote)[^\[\]]*?data-userid[^\[\]]*?](?:.*?(?R)*.*?)*?\[/\1]#isu', '', (string) $text );
990 }
991
992 function wpforo_strip_urls( $text ) {
993 return preg_replace( '#(?:[^\'\"]|^)(https?://[^\s\'\"<>]+)(?:[^\'\"]|$)#iu', '', (string) $text );
994 }
995
996 function wpforo_text( $text, $length = 0, $echo = true, $strip_tags = true, $strip_urls = true, $strip_shortcodes = true, $strip_quotes = true ) {
997 $text = (string) $text;
998 $text = str_replace( '</p>', '</p> ', $text );
999 $text = str_replace( '</div>', '</div> ', $text );
1000
1001 if( $strip_quotes ) $text = wpforo_strip_quotes( $text );
1002 if( $strip_urls ) $text = wpforo_strip_urls( $text );
1003 if( $strip_tags ) $text = strip_tags( (string) $text );
1004 if( $strip_shortcodes ) $text = wpforo_strip_shortcodes( $text );
1005
1006 $text = apply_filters( 'wpforo_text', $text, $length, $echo, $strip_tags, $strip_urls, $strip_shortcodes, $strip_quotes );
1007
1008 $text = trim( str_replace( "\xc2\xa0", ' ', (string) $text ) );
1009
1010 if( ! $length ) {
1011 $text = trim( (string) $text );
1012 } elseif( function_exists( 'mb_substr' ) ) {
1013 $text = trim(
1014 mb_substr( (string) $text, 0, $length, get_option( 'blog_charset' ) ) . ( ( function_exists( 'mb_strlen' ) ? mb_strlen( (string) $text, get_option( 'blog_charset' ) ) : strlen(
1015 (string) $text
1016 ) ) > $length ? '...' : '' )
1017 );
1018 } else {
1019 $text = trim( substr( (string) $text, 0, $length ) . ( strlen( (string) $text ) > $length ? '...' : '' ) );
1020 }
1021
1022 if( $echo ) echo $text;
1023
1024 return $text;
1025 }
1026
1027 function wpforo_admin_options_tabs( $tabs, $current = 'general', $subtab = false, $sub_current = 'general' ) {
1028 if( ! empty( $tabs ) ) {
1029 $class_attr = $subtab ? 'vert_tab' : '';
1030 echo '<h2 class="nav-tab-wrapper ' . esc_attr( $class_attr ) . '">';
1031 foreach( $tabs as $tab => $name ) {
1032 $class = ( $tab === $current || ( $subtab && $tab === $sub_current ) ) ? ' nav-tab-active' : '';
1033 $sub = $subtab ? '&subtab=' . esc_attr( $tab ) : '';
1034 $class = esc_attr( $class );
1035 $current = esc_attr( $current );
1036 $tab = esc_attr( $tab );
1037 $sub = esc_attr( $sub );
1038 echo "<a class='nav-tab $class $class_attr' href='?page=" . (string) wpfval( $_GET, 'page' ) . "&tab=" . ( $subtab ? $current : $tab ) . "$sub'>$name</a>";
1039 }
1040 echo '</h2>';
1041 }
1042 }
1043
1044 function wpforo_admin_tools_tabs( $tabs, $current = 'antispam', $subtab = false, $sub_current = 'antispam' ) {
1045 if( ! empty( $tabs ) ) {
1046 $class_attr = $subtab ? 'vert_tab' : '';
1047 echo '<h2 class="nav-tab-wrapper ' . esc_attr( $class_attr ) . '">';
1048 foreach( $tabs as $tab => $name ) {
1049 $class = ( $tab == $current || ( $subtab && $tab == $sub_current ) ) ? ' nav-tab-active' : '';
1050 $sub = $subtab ? '&subtab=' . esc_attr( $tab ) : '';
1051 $class = esc_attr( $class );
1052 $current = esc_attr( $current );
1053 $tab = esc_attr( $tab );
1054 $sub = esc_attr( $sub );
1055 $name = esc_html( $name );
1056 echo "<a class='nav-tab$class $class_attr' href='?page=" . wpforo_prefix_slug( 'tools' ) . "&tab=" . ( $subtab ? $current : $tab ) . "$sub' style='float:left'>$name</a>";
1057 }
1058 echo '</h2>';
1059 }
1060 }
1061
1062 function wpforo_content_filter( $content, $post = [] ) {
1063 $content = (string) $content;
1064 if( strpos( (string) $content, '../' ) !== false ) {
1065 $home_url = trim( preg_replace( [ '#/?\?.*$#isu', '#index\.php/?#isu' ], '', home_url() ), '/\\' ) . '/';
1066 $content = preg_replace( '#((?:href|src)=[\'\"])(?:https?://)?(?:\.+/)+wp-content/#i', "$1" . $home_url . "wp-content/", (string) $content );
1067 }
1068 $content = apply_filters( 'wpforo_body_text_filter', $content, $post );
1069 if( apply_filters( 'wpforo_auto_embed_image', true, $post ) ) {
1070 $content = preg_replace(
1071 '#([^\'\"]|^)(https?://[^\s\'\"<>]+\.(?:jpg|jpeg|png|webp|gif|svg|bmp|tiff))([^\'\"]|$)#iu',
1072 '$1 <a class="wpforo-auto-embeded-link" href="$2" target="_blank"><img class="wpforo-auto-embeded-image" src="$2"/></a> $3',
1073 (string) $content
1074 );
1075 }
1076 if( apply_filters( 'wpforo_auto_embed_link', true, $post ) ) {
1077 $content = preg_replace(
1078 '#([^\'\"]|^)(https?://[^\s\'\"<>\[\]]+)([^\'\"]|$)#iu',
1079 '$1 <a class="wpforo-auto-embeded-link" href="$2" target="_blank">$2</a> $3',
1080 (string) $content
1081 );
1082 }
1083 if( preg_match_all( '#<pre([^<>]*)>(.*?class=[\'"]wpforo-auto-embeded[^\'"]*[\'"].*?)</pre>#isu', (string) $content, $matches, PREG_SET_ORDER ) ) {
1084 foreach( $matches as $match ) {
1085 $match[2] = preg_replace( '#<img[^<>]*class=[\'"]wpforo-auto-embeded-image[\'"][^<>]*src=[\'"]([^\'"]*)[\'"][^<>]*>#isu', '$1', $match[2] );
1086 $match[2] = preg_replace( '#<a[^<>]*class=[\'"]wpforo-auto-embeded-link[\'"][^<>]*href=[\'"]([^\'"]*)[\'"][^<>]*>.*?</a>#isu', '$1', $match[2] );
1087 $content = str_replace( $match[0], '<pre' . $match[1] . '>' . $match[2] . '</pre>', $content );
1088 }
1089 }
1090 $content = preg_replace( '#(<a[^<>]*>[^<>]*)<a[^<>]*class=[\'"]wpforo-auto-embeded-link[\'"][^<>]*href=[\'"]([^\'"]*)[\'"][^<>]*>[^<>]*</a>([^<>]*</a>)#iu', '$1$2$3', (string) $content );
1091 $content = apply_filters( 'wpforo_content_filter', $content, $post );
1092
1093 return wpautop( $content );
1094 }
1095
1096 function wpforo_remove_links( $content ) {
1097 return preg_replace( '#([^\'\"]|^)(https?://[^\s\'\"<>]+)([^\'\"]|$)#isu', '$1 [' . wpforo_phrase( 'removed link', false ) . '] $3', (string) $content );
1098 }
1099
1100 add_filter( 'wpforo_content_filter', 'wpforo_nofollow_tag', 20 );
1101 function wpforo_nofollow_tag( $content ) {
1102 return preg_replace_callback( '#<a[^><]*?href=[\'\"]([^\'\"]+)[\'\"][^><]*?>#isu', 'wpforo_nofollow', (string) $content );
1103 }
1104
1105 function wpforo_nofollow( $match ) {
1106 $ret = $match[0];
1107 if( apply_filters( 'wpforo_external_link_nofollow', true ) ) {
1108 $dofollow = wpforo_setting( 'seo', 'dofollow' );
1109 $request_uri_parse = parse_url( wpforo_get_request_uri() );
1110 $main_host = preg_replace( '#^.*?([^.]+?\.[^.]+?)$#isu', '$1', (string) $request_uri_parse['host'] );
1111 $link_url = parse_url( $match[1] );
1112 if( strpos( (string) $match[1], $main_host ) === false && ! ( ! empty( $dofollow ) && ! empty( $link_url['host'] ) && in_array( $link_url['host'], $dofollow ) ) ) {
1113 $ret = preg_replace_callback(
1114 '#\srel=[\'\"]([^\'\"]*?)[\'\"]#iu',
1115 function( $m ) {
1116 $rels = array_filter( preg_split( '#\s#u', $m[1] ) );
1117 $rels = array_merge( [ 'nofollow' ], $rels );
1118
1119 return sprintf( 'rel="%1$s"', implode( ' ', $rels ) );
1120 },
1121 (string) $match[0],
1122 1,
1123 $count
1124 );
1125 if( ! $count ) $ret = str_replace( '>', ' rel="nofollow">', $match[0] );
1126 }
1127 }
1128
1129 return $ret;
1130 }
1131
1132 add_action( 'wpforo_actions_end', 'wpforo_logs', 10 );
1133 function wpforo_logs() {
1134 if( ! WPF()->current_object['is_404'] ) {
1135 WPF()->log->read();
1136 WPF()->log->visit();
1137 }
1138 }
1139
1140 add_action( 'wpforo_bottom_hook', 'wpforo_user_logging' );
1141 function wpforo_user_logging() {
1142 $data = WPF()->current_object;
1143 $filter_views = apply_filters( 'wpforo_filter_topic_views', true );
1144 if( wpfval( $data, 'template' ) && $data['template'] == 'post' && wpfval( $data, 'topicid' ) ) {
1145 if( $filter_views ) {
1146 //to-do: don't increase views before all read point.
1147 if( wpforo_setting( 'legal', 'cookies' ) ) {
1148 $viwed_ids = wpforo_getcookie( wpforo_prefix( 'read_topics' ), false );
1149 $has_viewed = ! empty( $viwed_ids ) && wpfval( $viwed_ids, $data['topicid'] );
1150 if( ! $has_viewed && is_user_logged_in() ) {
1151 $viwed_db_ids = wpforo_current_usermeta( wpforo_prefix( 'read_topics' ) );
1152 $has_viewed = ! empty( $viwed_db_ids ) && wpfval( $viwed_db_ids, $data['topicid'] );
1153 }
1154 if( ! $has_viewed ) {
1155 WPF()->db->query( "UPDATE `" . WPF()->tables->topics . "` SET `views` = `views` + 1 WHERE `topicid` = " . intval( $data['topicid'] ) );
1156 }
1157 } elseif( is_user_logged_in() ) {
1158 if( wpfval( WPF()->current_usermeta, wpforo_prefix( 'read_topics' ) ) ) {
1159 $viwed_db_ids = wpforo_current_usermeta( wpforo_prefix( 'read_topics' ) );
1160 if( empty( $viwed_db_ids ) || ! wpfval( $viwed_db_ids, $data['topicid'] ) ) {
1161 WPF()->db->query( "UPDATE `" . WPF()->tables->topics . "` SET `views` = `views` + 1 WHERE `topicid` = " . intval( $data['topicid'] ) );
1162 }
1163 } else {
1164 WPF()->db->query( "UPDATE `" . WPF()->tables->topics . "` SET `views` = `views` + 1 WHERE `topicid` = " . intval( $data['topicid'] ) );
1165 }
1166 }
1167 } else {
1168 WPF()->db->query( "UPDATE `" . WPF()->tables->topics . "` SET `views` = `views` + 1 WHERE `topicid` = " . intval( $data['topicid'] ) );
1169 }
1170 }
1171 }
1172
1173 function wpforo_setcookie( $key, $args = [], $implode = false ) {
1174 if( ! wpforo_setting( 'legal', 'cookies' ) ) return;
1175 if( ! empty( $args ) && is_array( $args ) ) {
1176 $num = count( $args );
1177 if( $num > 3 ) {
1178 $max = apply_filters( 'wpforo_cookie_max_logged_topics', 10 );
1179 $delta = $num - $max;
1180 if( $delta > 0 ) $args = array_slice( $args, $delta, null, true );
1181 }
1182 }
1183 if( ! empty( $args ) && is_array( $args ) && $implode ) {
1184 $value = trim( implode( ',', $args ), ',' );
1185 } elseif( ! empty( $args ) && is_array( $args ) && ! $implode ) {
1186 $value = wp_json_encode( $args );
1187 }
1188 if( ! isset( $value ) ) $value = '';
1189 if( $key ) {
1190 $secure = is_ssl();
1191 $secure_logged_in_cookie = $secure && 'https' === parse_url( get_option( 'home' ), PHP_URL_SCHEME );
1192 if( COOKIEPATH != SITECOOKIEPATH ) {
1193 @setcookie( $key, $value, time() + 7776000, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true );
1194 } else {
1195 @setcookie( $key, $value, time() + 7776000, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true );
1196 }
1197 }
1198 }
1199
1200 function wpforo_getcookie( $key, $explode = false ) {
1201 if( ! wpforo_setting( 'legal', 'cookies' ) ) return false;
1202 if( $cookie = wpfval( $_COOKIE, $key ) ) {
1203 if( $explode ) {
1204 return explode( ',', $cookie );
1205 } else {
1206 $cookie = wp_unslash( $cookie );
1207 if( ! $data = json_decode( $cookie, true ) ) return $cookie;
1208
1209 return $data;
1210 }
1211 }
1212
1213 return false;
1214 }
1215
1216 function wpforo_is_bot() {
1217 static $is_bot = null;
1218
1219 if ( $is_bot !== null ) {
1220 return $is_bot;
1221 }
1222
1223 if( ! $http_user_agent = wpfval( $_SERVER, 'HTTP_USER_AGENT' ) ) $http_user_agent = wpfval( $_SERVER, 'http_user_agent' );
1224 if( $http_user_agent ) {
1225 $is_bot = (bool) preg_match(
1226 '#(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',
1227 (string) $http_user_agent
1228 );
1229
1230 return $is_bot;
1231 }
1232
1233 $is_bot = true;
1234
1235 return $is_bot;
1236 }
1237
1238 //Option value filter and output
1239 function wpfo( $option = '', $echo = true, $esc = 'esc_attr' ) {
1240 if( is_string( $option ) ) {
1241 $option = stripslashes( (string) $option );
1242 if( $esc === 'esc_attr' ) {
1243 $option = esc_attr( $option );
1244 } elseif( $esc === 'esc_html' ) {
1245 $option = esc_html( $option );
1246 } elseif( $esc === 'esc_url' ) {
1247 $option = esc_url( (string) $option );
1248 } elseif( $esc === 'esc_textarea' ) {
1249 $option = esc_textarea( (string) $option );
1250 }
1251 }
1252
1253 if( $echo ) {
1254 echo $option;
1255 } else {
1256 return $option;
1257 }
1258 }
1259
1260 //Option maker for checkbox, radio and select
1261 function wpfo_check( $option = '', $value = '', $type = 'checked', $echo = true ) {
1262 $option = ( isset( $option ) && isset( $value ) && $option == $value ) ? $type : '';
1263 if( $echo ) {
1264 echo $option;
1265 } else {
1266 return $option;
1267 }
1268 }
1269
1270 /**
1271 * Check if nested array keys exist.
1272 *
1273 * Traverses nested arrays to validate if a path of keys exists.
1274 * All arguments after $array are treated as keys to traverse - this function
1275 * does NOT support a default value parameter.
1276 *
1277 * @example
1278 * // Check if $arr['user']['profile']['name'] exists:
1279 * wpfkey( $arr, 'user', 'profile', 'name' ) // Returns true/false
1280 *
1281 * // WRONG - this tries to check $arr['key'][0], not use 0 as default:
1282 * wpfkey( $arr, 'key', 0 ) // Checks for $arr['key'][0], NOT a default!
1283 *
1284 * @param array $array The array to check
1285 * @param string|int $key First key to check
1286 * @param string|int ...$_ Additional keys to traverse (NOT a default value!)
1287 *
1288 * @return bool True if all keys exist in the nested path, false otherwise
1289 *
1290 * @see wpfval() To get the value at a nested path
1291 * @see wpffix() To get a value with a default fallback
1292 */
1293 function wpfkey( &$array, $key ) {
1294 $a = $array;
1295 foreach( func_get_args() as $arg_num => $key ) {
1296 if( $arg_num === 0 ) continue;
1297 if( is_array( $a ) && ( is_string( $key ) || is_int( $key ) ) && array_key_exists( $key, $a ) ) {
1298 $a = $a[ $key ];
1299 } else {
1300 return false;
1301 }
1302 }
1303
1304 return true;
1305 }
1306
1307 /**
1308 * Get value from nested array keys, or null if path doesn't exist.
1309 *
1310 * Traverses nested arrays to retrieve a value at a given key path.
1311 * All arguments after $array are treated as keys to traverse - this function
1312 * does NOT support a default value parameter.
1313 *
1314 * IMPORTANT: This function does NOT accept a default value!
1315 * Use wpffix() or the null coalescing operator for defaults.
1316 *
1317 * @example
1318 * // Get $arr['user']['profile']['name']:
1319 * wpfval( $arr, 'user', 'profile', 'name' ) // Returns value or null
1320 *
1321 * // WRONG - this tries to access $arr['key'][0], not use 0 as default:
1322 * wpfval( $arr, 'key', 0 ) // Accesses $arr['key'][0], NOT a default!
1323 *
1324 * // CORRECT - use ?: or ?? for defaults:
1325 * wpfval( $arr, 'key' ) ?: 'default' // Fallback for falsy values
1326 * wpfval( $arr, 'key' ) ?? 'default' // Fallback for null only
1327 *
1328 * // CORRECT - use wpffix() for defaults:
1329 * wpffix( 'default', $arr, 'key' ) // Returns 'default' if key missing
1330 *
1331 * @param mixed $array The array to traverse
1332 * @param string|int ...$_ Keys to traverse (NOT a default value!)
1333 *
1334 * @return mixed|null The value at the key path, or null if any key doesn't exist
1335 *
1336 * @see wpfkey() To check if a nested path exists
1337 * @see wpffix() To get a value with a default fallback
1338 */
1339 function wpfval( $array ) {
1340 if( func_num_args() === 1 ) return $array;
1341 $a = $array;
1342 foreach( func_get_args() as $arg_num => $key ) {
1343 if( $arg_num === 0 ) continue;
1344 if( is_array( $a ) && ( is_string( $key ) || is_int( $key ) ) && array_key_exists( $key, $a ) ) {
1345 $a = $a[ $key ];
1346 } else {
1347 return null;
1348 }
1349 }
1350
1351 return $a;
1352 }
1353
1354 /**
1355 * Always return the preferred default value if the array, or array index is undefined.
1356 *
1357 * @param mixed the default value if current index is undefined in the array
1358 * @param mixed the $array to be checked
1359 * @param string|int ... $_, ... [optional] more keys
1360 *
1361 * @return mixed|null
1362 */
1363 function wpffix( $return, $array, $a = null, $b = null, $c = null ) {
1364 $value = wpfval( $array, $a, $b, $c );
1365 if( ! is_null( $value ) ) {
1366 return $value;
1367 }
1368
1369 return $return;
1370 }
1371
1372 function wpforo_human_filesize( $bytes, $decimals = 2 ) {
1373 $size = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];
1374 $factor = floor( ( strlen( (string) $bytes ) - 1 ) / 3 );
1375
1376 return sprintf( "%.{$decimals}f", $bytes / pow( 1024, $factor ) ) . '&nbsp;' . @$size[ $factor ];
1377 }
1378
1379 function wpforo_human_time_diff( int $from, bool $ago = true, int $to = 0 ): string {
1380 if( ! $to ) $to = time();
1381 $d = human_time_diff( $from, $to );
1382 if( $ago ) $d = sprintf( wpforo_phrase( '%s ago', false, false ), $d );
1383
1384 return $d;
1385 }
1386
1387 /**
1388 * @param int | string $date
1389 *
1390 * @return int
1391 */
1392 function wpforo_gmtimestamp( $date = 0 ): int {
1393 if( ! $date ) $date = time();
1394
1395 return intval( is_numeric( $date ) ? $date : strtotime( $date . ' GMT' ) );
1396 }
1397
1398 function wpforo_timezone( string $timezone = '' ): array {
1399 $timezone_string = '';
1400 $current_offset = null;
1401 if( $timezone ) {
1402 if( $timezone === 'wp' ) {
1403 $timezone_string = get_option( 'timezone_string', '' );
1404 $current_offset = get_option( 'gmt_offset', null );
1405 } else {
1406 if( preg_match( '#UTC\s*([-+])\s*([\d.]+)#i', (string) $timezone, $timezone_array ) ) {
1407 $current_offset = $timezone_array[1] . $timezone_array[2];
1408 } elseif( in_array( $timezone, timezone_identifiers_list() ) ) {
1409 $timezone_string = $timezone;
1410 }
1411 }
1412 }
1413
1414 return compact( 'timezone_string', 'current_offset' );
1415 }
1416
1417 /**
1418 * @param int | string $date
1419 * @param string $timezone
1420 *
1421 * @return int
1422 */
1423 function wpforo_timestamp( $date = 0, string $timezone = '' ): int {
1424 $timestamp = wpforo_gmtimestamp( $date );
1425 $tmz = wpforo_timezone( $timezone );
1426
1427 if( is_numeric( $tmz['current_offset'] ) ) {
1428 $timestamp += $tmz['current_offset'] * 3600;
1429 } elseif( $tmz['timezone_string'] && class_exists( 'DateTime' ) && class_exists( 'DateTimeZone' ) ) {
1430 try {
1431 $dt = new DateTime( gmdate( 'Y-m-d H:i:s', $timestamp ), new DateTimeZone( $tmz['timezone_string'] ) );
1432 $timestamp += $dt->getOffset();
1433 } catch ( Exception $e ) {
1434 }
1435 }
1436
1437 return (int) $timestamp;
1438 }
1439
1440 /**
1441 * @param int $gmttime
1442 * @param string $format
1443 * @param bool $wp_date_format
1444 *
1445 * @return string
1446 */
1447 function wpforo_date_format( int $gmttime, string $format, bool $wp_date_format = true ): string {
1448 if( $format === 'mysql' ) {
1449 return 'Y-m-d H:i:s';
1450 } elseif( in_array( $format, [ 'human', 'ago', 'timestamp' ], true ) ) {
1451 return $format;
1452 }
1453
1454 if( $format === 'ago-date' ) {
1455 $diff = time() - $gmttime;
1456 if( $diff > 31536000 ) {
1457 return apply_filters( 'wpforo_date_format_ago_date_year', 'M d, y' );
1458 } elseif( $diff > 518400 ) {
1459 return apply_filters( 'wpforo_date_format_ago_date_month', 'M d' );
1460 } else {
1461 return 'ago';
1462 }
1463 }
1464
1465 $sep = ' ';
1466 if( $wp_date_format ) {
1467 $date_format = get_option( 'date_format' );
1468 $time_format = get_option( 'time_format' );
1469 } else {
1470 $date_format = 'Y-m-d';
1471 $time_format = 'H:i:s';
1472 }
1473 if( $format === 'datetime' ) {
1474 $format = $date_format . $sep . $time_format;
1475 } elseif( $format === 'date' ) {
1476 $format = $date_format;
1477 } elseif( $format === 'time' ) {
1478 $format = $time_format;
1479 } elseif( $wp_date_format ) {
1480 $format = $date_format . $sep . $time_format;
1481 }
1482
1483 return $format;
1484 }
1485
1486 /**
1487 * @param $date string | int
1488 * @param $format string
1489 * @param $timezone string
1490 *
1491 * @return string | int
1492 */
1493 function _wpforo_date( $date = 0, string $format = 'Y-m-d H:i:s', string $timezone = '' ) {
1494 if( in_array( $format, [ 'human', 'ago' ], true ) ) {
1495 $d = wpforo_human_time_diff( wpforo_gmtimestamp( $date ), $format === 'ago' );
1496 } else {
1497 $timestamp = wpforo_timestamp( $date, $timezone );
1498 if( $format === 'timestamp' ) {
1499 $d = $timestamp;
1500 } else {
1501 if( $format === 'mysql' ) $format = 'Y-m-d H:i:s';
1502 $d = date_i18n( $format, $timestamp );
1503 }
1504 }
1505
1506 return $d;
1507 }
1508
1509 /**
1510 * @param int | string $date
1511 * @param string $format
1512 * @param bool $echo
1513 * @param bool $wp_date_format
1514 * @param string|null $timezone
1515 *
1516 * @return string | int
1517 */
1518 function wpforo_date( $date, $format = 'ago', $echo = true, $wp_date_format = true, $timezone = null ) {
1519 $wp_date_format = $wp_date_format && wpforo_setting( 'general', 'wp_date_format' );
1520 $format = wpforo_date_format( wpforo_gmtimestamp( $date ), $format, $wp_date_format );
1521
1522 if( is_null( $timezone ) ) $timezone = ( (string) wpfval( WPF()->current_user, 'timezone' ) ?: ( (string) wpfval( $_COOKIE, 'wpforo_browser_timezone' ) ?: 'wp' ) );
1523 $timezone = apply_filters( 'wpforo_date_timezone', $timezone );
1524
1525 $d = _wpforo_date( $date, $format, $timezone );
1526 $d = apply_filters( 'wpforo_date', $d, $date, $format, $timezone );
1527
1528 if( $echo ) echo $d;
1529
1530 return $d;
1531 }
1532
1533 if( ! function_exists( 'wpforo_date_raw' ) ) {
1534 /**
1535 * @param string|int $date
1536 * @param string $format
1537 * @param bool $echo
1538 * @param string|null $timezone
1539 *
1540 * @return string | int
1541 */
1542 function wpforo_date_raw( $date, $format = 'ago', $echo = true, $timezone = null ) {
1543 return wpforo_date( $date, $format, $echo, false, $timezone );
1544 }
1545 }
1546
1547 function wpforo_write_file( string $new_file, $content ): array {
1548 $return = [ 'error' => '', 'file' => '' ];
1549 $new_file = wpforo_fix_dir_sep( $new_file );
1550 $dir = dirname( $new_file );
1551 if( ! is_dir( $dir ) ) wp_mkdir_p( $dir );
1552 $ifp = @fopen( $new_file, 'wb' );
1553 if( ! $ifp ) {
1554 if( is_resource( $ifp ) ) {
1555 @fclose( $ifp );
1556 }
1557 $return = [ 'error' => sprintf( __( 'Could not write file %s' ), $new_file ) ];
1558 } else {
1559 @fwrite( $ifp, $content );
1560 fclose( $ifp );
1561 clearstatcache();
1562 // Set correct file permissions
1563 $stat = @stat( dirname( $new_file ) );
1564 $perms = $stat['mode'] & 0007777;
1565 $perms = $perms & 0000666;
1566 @chmod( $new_file, $perms );
1567 clearstatcache();
1568 $return['file'] = $new_file;
1569 }
1570
1571 return $return;
1572 }
1573
1574 function wpforo_get_file_content( $file ) {
1575 if( is_file( $file ) ) {
1576 $fp = @fopen( $file, 'r' );
1577 if( ! $fp ) {
1578 if( is_resource( $fp ) ) {
1579 @fclose( $fp );
1580 }
1581 } else {
1582 $size = @filesize( $file );
1583 if( $size > 0 ) {
1584 $file_data = fread( $fp, $size );
1585 fclose( $fp );
1586
1587 return $file_data;
1588 }
1589 @fclose( $fp );
1590 }
1591 }
1592
1593 return false;
1594 }
1595
1596 ################################################################################
1597 /**
1598 * Clears file basename and removes trailing slash
1599 *
1600 * @param string filename
1601 *
1602 * @return string
1603 * @since 1.0.0
1604 *
1605 */
1606 function wpforo_clear_basename( $file ) {
1607 $file = (string) $file;
1608 $file = str_replace( '\\', '/', $file );
1609 $file = preg_replace( '|/+|', '/', $file );
1610 $file = trim( (string) $file, '/' );
1611
1612 return $file;
1613 }
1614
1615 #################################################################################
1616 /**
1617 * Removes directory with all files and folders
1618 *
1619 * @param string directory name
1620 *
1621 * @since 1.0.0
1622 *
1623 */
1624 function _wpforo_remove_directory( $directory ) {
1625 $directory = (string) $directory;
1626 $directory = wpforo_fix_dir_sep( $directory );
1627 $directory_ns = trim( $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
1628 $directory_ws = DIRECTORY_SEPARATOR . trim( $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
1629 $glob = glob( $directory_ns . '*' );
1630 if( empty( $glob ) ) $glob = glob( $directory_ws . '*' );
1631 foreach( $glob as $item ) {
1632 if( is_dir( $item ) ) {
1633 _wpforo_remove_directory( $item );
1634 } else {
1635 unlink( $item );
1636 }
1637 }
1638
1639 return rmdir( $directory );
1640 }
1641
1642 #################################################################################
1643 /**
1644 * Removes directory with all files and folders
1645 *
1646 * @param string directory name
1647 *
1648 * @since 1.0.0
1649 *
1650 */
1651 function wpforo_remove_directory( $file, $recursive = true ) {
1652 if( ! class_exists( 'WP_Filesystem_Direct' ) ) {
1653 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
1654 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
1655 }
1656 $file = wpforo_fix_dir_sep( $file );
1657 $WP_Filesystem_Direct = new WP_Filesystem_Direct( null );
1658
1659 return $WP_Filesystem_Direct->delete( $file, $recursive );
1660 }
1661
1662 #################################################################################
1663 /**
1664 * Converts bytes to KB, MB, GB
1665 *
1666 * @param integer Bytes
1667 *
1668 * @return string
1669 * @since 1.0.0
1670 *
1671 */
1672 function wpforo_print_size( $value, $points = true ) {
1673 if( $value < 1024 ) {
1674 return $value . ( ( $points ) ? "B" : '' );
1675 } elseif( $value >= 1024 && $value < ( 1024 * 1024 ) ) {
1676 $value = round( ( $value / 1024 ) * 10 ) / 10;
1677
1678 return $value . ( ( $points ) ? "KB" : '' );
1679 } elseif( $value >= 1024 * 1024 && $value < 1024 * 1024 * 1024 ) {
1680 $value = round( ( $value / ( 1024 * 1024 ) ) * 10 ) / 10;
1681
1682 return $value . ( ( $points ) ? "MB" : '' );
1683 } elseif( $value >= 1024 * 1024 * 1024 && $value <= 1024 * 1024 * 1024 * 1024 ) {
1684 $value = round( ( $value / ( 1024 * 1024 * 1024 ) ) * 10 ) / 10;
1685
1686 return $value . ( ( $points ) ? "GB" : '' );
1687 } else {
1688 $value = round( ( $value / ( 1024 * 1024 * 1024 * 1024 ) ) * 10 ) / 10;
1689
1690 return $value . ( ( $points ) ? "TB" : '' );
1691 }
1692 }
1693
1694 function wpforo_human_size_to_bytes( $sSize ) {
1695 if( is_numeric( $sSize ) ) return $sSize;
1696
1697 $sSuffix = substr( (string) $sSize, - 1 );
1698 $iValue = substr( (string) $sSize, 0, - 1 );
1699 switch( strtoupper( (string) $sSuffix ) ) {
1700 case 'M':
1701 $iValue *= 1024 * 1024;
1702 break;
1703 case 'K':
1704 $iValue *= 1024;
1705 break;
1706 case 'G':
1707 $iValue *= 1024 * 1024 * 1024;
1708 break;
1709 case 'T':
1710 $iValue *= 1024 * 1024 * 1024 * 1024;
1711 break;
1712 case 'P':
1713 $iValue *= 1024 * 1024 * 1024 * 1024 * 1024;
1714 break;
1715 }
1716
1717 return $iValue;
1718 }
1719
1720 function wpforo_print_number( $n, $echo = false ) {
1721 $x = str_replace( ",", "", $n );
1722 $x = intval( $x );
1723 $n = 0 + $x;
1724 $number = 0;
1725 if( ! is_numeric( $n ) ) return false;
1726 if( $n > 1000000000000 ) {
1727 $number = round( ( $n / 1000000000000 ), 1 ) . ' ' . str_replace( '{number}', '', wpforo_phrase( '{number}T', false ) );
1728 } else if( $n > 1000000000 ) {
1729 $number = round( ( $n / 1000000000 ), 1 ) . ' ' . str_replace( '{number}', '', wpforo_phrase( '{number}B', false ) );
1730 } else if( $n > 1000000 ) {
1731 $number = round( ( $n / 1000000 ), 1 ) . ' ' . str_replace( '{number}', '', wpforo_phrase( '{number}M', false ) );
1732 } else if( $n > 10000 ) $number = round( ( $n / 1000 ), 1 ) . ' ' . str_replace( '{number}', '', wpforo_phrase( '{number}K', false ) );
1733
1734 $number = ( $number ) ? $number : number_format( $n );
1735
1736 if( $echo ) {
1737 echo $number;
1738 } else {
1739 return $number;
1740 }
1741 }
1742
1743 function wpforo_bigintval( $value ) {
1744 $value = wpforo_settype( $value, 'string' );
1745 if( is_string( $value ) ) {
1746 $value = trim( $value );
1747 if( ! is_numeric( $value ) ) {
1748 $value = preg_replace( "#[^0-9].*#s", '', (string) $value );
1749 if( ! is_numeric( $value ) ) $value = 0;
1750 }
1751 } else {
1752 $value = 0;
1753 }
1754
1755 return ( strlen( (string) $value ) < strlen( (string) PHP_INT_MAX ) ) ? (int) $value : $value;
1756 }
1757
1758 function wpforo_removebb( $string ) {
1759 if( isset( $string ) && $string ) {
1760 $string = preg_replace( '|\[/*[^]\[]+]|i', '', (string) $string );
1761 }
1762
1763 return $string;
1764 }
1765
1766 function wpforo_file_upload_error( $code ): string {
1767 switch( $code ) {
1768 case UPLOAD_ERR_INI_SIZE:
1769 $message = wpforo_phrase( "The uploaded file exceeds the upload_max_filesize directive in php.ini", false );
1770 break;
1771 case UPLOAD_ERR_FORM_SIZE:
1772 $message = wpforo_phrase( "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form", false );
1773 break;
1774 case UPLOAD_ERR_PARTIAL:
1775 $message = wpforo_phrase( "The uploaded file was only partially uploaded", false );
1776 break;
1777 case UPLOAD_ERR_NO_FILE:
1778 $message = wpforo_phrase( "No file was uploaded", false );
1779 break;
1780 case UPLOAD_ERR_NO_TMP_DIR:
1781 $message = wpforo_phrase( "Missing a temporary folder", false );
1782 break;
1783 case UPLOAD_ERR_CANT_WRITE:
1784 $message = wpforo_phrase( "Failed to write file to disk", false );
1785 break;
1786 case UPLOAD_ERR_EXTENSION:
1787 $message = wpforo_phrase( "File upload stopped by extension", false );
1788 break;
1789 default:
1790 $message = wpforo_phrase( "Unknown upload error", false ) . ": " . $code;
1791 break;
1792 }
1793
1794 return $message;
1795 }
1796
1797 //$key allowed values are post, strip, data, user_description entities or the name of a field filter such as pre_user_description.
1798 //More info https://core.trac.wordpress.org/browser/tags/4.5.2/src/wp-includes/kses.php#L624
1799 function wpforo_kses( $string = '', $key = 'post' ) {
1800 if( ! $string || ! $key ) return $string;
1801 if( $key === 'email' ) {
1802 if( ! preg_match( '#<p[^<>]*?>#iu', (string) $string ) ) $string = wpautop( $string );
1803 $allowed_html = [
1804 'a' => [ 'href' => true, 'title' => true, 'style' => true ],
1805 'img' => [
1806 'src' => true,
1807 'width' => true,
1808 'height' => true,
1809 'align' => true,
1810 'alt' => true,
1811 'style' => true,
1812 ],
1813 'blockquote' => [ 'style' => true ],
1814 'h1' => [ 'style' => true ],
1815 'h2' => [ 'style' => true ],
1816 'h3' => [ 'style' => true ],
1817 'h4' => [ 'style' => true ],
1818 'h5' => [ 'style' => true ],
1819 'h6' => [ 'style' => true ],
1820 'hr' => [],
1821 'br' => [],
1822 'p' => [ 'style' => true ],
1823 'strong' => [ 'style' => true ],
1824 'em' => [ 'style' => true ],
1825 'del' => [ 'style' => true ],
1826 'style' => [],
1827 ];
1828 $allowed_html = apply_filters( 'wpforo_kses_allowed_html_email', $allowed_html );
1829 } elseif( $key === 'user_description' ) {
1830 $allowed_html = wp_kses_allowed_html( $key );
1831 $allowed_html['img'] = [
1832 'alt' => true,
1833 'align' => true,
1834 'border' => true,
1835 'height' => true,
1836 'hspace' => true,
1837 'longdesc' => true,
1838 'vspace' => true,
1839 'src' => true,
1840 'usemap' => true,
1841 'width' => true,
1842 ];
1843 $allowed_html['br'] = [];
1844 $allowed_html = apply_filters( 'wpforo_kses_allowed_html_user_description', $allowed_html );
1845 } elseif( $key === 'svg' ) {
1846 $allowed_tags = [
1847 // HTML
1848 'a',
1849 'font',
1850 'image',
1851 'style',
1852
1853 // SVG
1854 'svg',
1855 'altglyph',
1856 'altglyphdef',
1857 'altglyphitem',
1858 'animatecolor',
1859 'animatemotion',
1860 'animatetransform',
1861 'circle',
1862 'clippath',
1863 'defs',
1864 'desc',
1865 'ellipse',
1866 'filter',
1867 'font',
1868 'g',
1869 'glyph',
1870 'glyphref',
1871 'hkern',
1872 'image',
1873 'line',
1874 'lineargradient',
1875 'marker',
1876 'mask',
1877 'metadata',
1878 'mpath',
1879 'path',
1880 'pattern',
1881 'polygon',
1882 'polyline',
1883 'radialgradient',
1884 'rect',
1885 'stop',
1886 'switch',
1887 'symbol',
1888 'text',
1889 'textpath',
1890 'title',
1891 'tref',
1892 'tspan',
1893 'use',
1894 'view',
1895 'vkern',
1896
1897 // SVG Filters
1898 'feBlend',
1899 'feColorMatrix',
1900 'feComponentTransfer',
1901 'feComposite',
1902 'feConvolveMatrix',
1903 'feDiffuseLighting',
1904 'feDisplacementMap',
1905 'feDistantLight',
1906 'feFlood',
1907 'feFuncA',
1908 'feFuncB',
1909 'feFuncG',
1910 'feFuncR',
1911 'feGaussianBlur',
1912 'feMerge',
1913 'feMergeNode',
1914 'feMorphology',
1915 'feOffset',
1916 'fePointLight',
1917 'feSpecularLighting',
1918 'feSpotLight',
1919 'feTile',
1920 'feTurbulence',
1921
1922 //text
1923 '#text',
1924 ];
1925 $allowed_attrs = [
1926 // HTML
1927 'about',
1928 'accept',
1929 'action',
1930 'align',
1931 'alt',
1932 'autocomplete',
1933 'background',
1934 'bgcolor',
1935 'border',
1936 'cellpadding',
1937 'cellspacing',
1938 'checked',
1939 'cite',
1940 'class',
1941 'clear',
1942 'color',
1943 'cols',
1944 'colspan',
1945 'coords',
1946 'crossorigin',
1947 'datetime',
1948 'default',
1949 'dir',
1950 'disabled',
1951 'download',
1952 'enctype',
1953 'encoding',
1954 'face',
1955 'for',
1956 'headers',
1957 'height',
1958 'hidden',
1959 'high',
1960 'href',
1961 'hreflang',
1962 'id',
1963 'integrity',
1964 'ismap',
1965 'label',
1966 'lang',
1967 'list',
1968 'loop',
1969 'low',
1970 'max',
1971 'maxlength',
1972 'media',
1973 'method',
1974 'min',
1975 'multiple',
1976 'name',
1977 'noshade',
1978 'novalidate',
1979 'nowrap',
1980 'open',
1981 'optimum',
1982 'pattern',
1983 'placeholder',
1984 'poster',
1985 'preload',
1986 'pubdate',
1987 'radiogroup',
1988 'readonly',
1989 'rel',
1990 'required',
1991 'rev',
1992 'reversed',
1993 'role',
1994 'rows',
1995 'rowspan',
1996 'spellcheck',
1997 'scope',
1998 'selected',
1999 'shape',
2000 'size',
2001 'sizes',
2002 'span',
2003 'srclang',
2004 'start',
2005 'src',
2006 'srcset',
2007 'step',
2008 'style',
2009 'summary',
2010 'tabindex',
2011 'title',
2012 'type',
2013 'usemap',
2014 'valign',
2015 'value',
2016 'version',
2017 'width',
2018 'xmlns',
2019
2020 // SVG
2021 'accent-height',
2022 'accumulate',
2023 'additivive',
2024 'alignment-baseline',
2025 'ascent',
2026 'attributename',
2027 'attributetype',
2028 'azimuth',
2029 'basefrequency',
2030 'baseline-shift',
2031 'begin',
2032 'bias',
2033 'by',
2034 'class',
2035 'clip',
2036 'clip-path',
2037 'clip-rule',
2038 'color',
2039 'color-interpolation',
2040 'color-interpolation-filters',
2041 'color-profile',
2042 'color-rendering',
2043 'cx',
2044 'cy',
2045 'd',
2046 'dx',
2047 'dy',
2048 'diffuseconstant',
2049 'direction',
2050 'display',
2051 'divisor',
2052 'dur',
2053 'edgemode',
2054 'elevation',
2055 'end',
2056 'fill',
2057 'fill-opacity',
2058 'fill-rule',
2059 'filter',
2060 'filterUnits',
2061 'flood-color',
2062 'flood-opacity',
2063 'font-family',
2064 'font-size',
2065 'font-size-adjust',
2066 'font-stretch',
2067 'font-style',
2068 'font-variant',
2069 'font-weight',
2070 'fx',
2071 'fy',
2072 'g1',
2073 'g2',
2074 'glyph-name',
2075 'glyphref',
2076 'gradientunits',
2077 'gradienttransform',
2078 'height',
2079 'href',
2080 'id',
2081 'image-rendering',
2082 'in',
2083 'in2',
2084 'k',
2085 'k1',
2086 'k2',
2087 'k3',
2088 'k4',
2089 'kerning',
2090 'keypoints',
2091 'keysplines',
2092 'keytimes',
2093 'lang',
2094 'lengthadjust',
2095 'letter-spacing',
2096 'kernelmatrix',
2097 'kernelunitlength',
2098 'lighting-color',
2099 'local',
2100 'marker-end',
2101 'marker-mid',
2102 'marker-start',
2103 'markerheight',
2104 'markerunits',
2105 'markerwidth',
2106 'maskcontentunits',
2107 'maskunits',
2108 'max',
2109 'mask',
2110 'media',
2111 'method',
2112 'mode',
2113 'min',
2114 'name',
2115 'numoctaves',
2116 'offset',
2117 'operator',
2118 'opacity',
2119 'order',
2120 'orient',
2121 'orientation',
2122 'origin',
2123 'overflow',
2124 'paint-order',
2125 'path',
2126 'pathlength',
2127 'patterncontentunits',
2128 'patterntransform',
2129 'patternunits',
2130 'points',
2131 'preservealpha',
2132 'preserveaspectratio',
2133 'r',
2134 'rx',
2135 'ry',
2136 'radius',
2137 'refx',
2138 'refy',
2139 'repeatcount',
2140 'repeatdur',
2141 'restart',
2142 'result',
2143 'rotate',
2144 'scale',
2145 'seed',
2146 'shape-rendering',
2147 'specularconstant',
2148 'specularexponent',
2149 'spreadmethod',
2150 'stddeviation',
2151 'stitchtiles',
2152 'stop-color',
2153 'stop-opacity',
2154 'stroke-dasharray',
2155 'stroke-dashoffset',
2156 'stroke-linecap',
2157 'stroke-linejoin',
2158 'stroke-miterlimit',
2159 'stroke-opacity',
2160 'stroke',
2161 'stroke-width',
2162 'style',
2163 'surfacescale',
2164 'tabindex',
2165 'targetx',
2166 'targety',
2167 'transform',
2168 'text-anchor',
2169 'text-decoration',
2170 'text-rendering',
2171 'textlength',
2172 'type',
2173 'u1',
2174 'u2',
2175 'unicode',
2176 'values',
2177 'viewbox',
2178 'visibility',
2179 'vector-effect',
2180 'vert-adv-y',
2181 'vert-origin-x',
2182 'vert-origin-y',
2183 'width',
2184 'word-spacing',
2185 'wrap',
2186 'writing-mode',
2187 'xchannelselector',
2188 'ychannelselector',
2189 'x',
2190 'x1',
2191 'x2',
2192 'xmlns',
2193 'y',
2194 'y1',
2195 'y2',
2196 'z',
2197 'zoomandpan',
2198
2199 // MathML
2200 'accent',
2201 'accentunder',
2202 'align',
2203 'bevelled',
2204 'close',
2205 'columnsalign',
2206 'columnlines',
2207 'columnspan',
2208 'denomalign',
2209 'depth',
2210 'dir',
2211 'display',
2212 'displaystyle',
2213 'fence',
2214 'frame',
2215 'height',
2216 // 'href',
2217 'id',
2218 'largeop',
2219 'length',
2220 'linethickness',
2221 'lspace',
2222 'lquote',
2223 'mathbackground',
2224 'mathcolor',
2225 'mathsize',
2226 'mathvariant',
2227 'maxsize',
2228 'minsize',
2229 'movablelimits',
2230 'notation',
2231 'numalign',
2232 'open',
2233 'rowalign',
2234 'rowlines',
2235 'rowspacing',
2236 'rowspan',
2237 'rspace',
2238 'rquote',
2239 'scriptlevel',
2240 'scriptminsize',
2241 'scriptsizemultiplier',
2242 'selection',
2243 'separator',
2244 'separators',
2245 'slope',
2246 'stretchy',
2247 'subscriptshift',
2248 'supscriptshift',
2249 'symmetric',
2250 'voffset',
2251 'width',
2252 'xmlns',
2253
2254 // XML
2255 // 'xlink:href',
2256 'xml:id',
2257 'xlink:title',
2258 'xml:space',
2259 'xmlns:xlink',
2260 ];
2261 $attrs = array_map( '__return_true', array_flip( $allowed_attrs ) );
2262 $allowed_html = array_map( function() use ( $attrs ) {
2263 return $attrs;
2264 }, array_flip( $allowed_tags ) );
2265 } else {
2266 global $allowedposttags;
2267 $allowed_html = $allowedposttags;
2268 if( wpforo_setting( 'posting', 'content_do_shortcode' ) ) {
2269 $allowed_html = wp_kses_allowed_html( $key );
2270 }
2271 $extra_html = wpforo_setting( 'posting', 'extra_html_tags' );
2272 $allowed_html = wpforo_extra_html_parser( $extra_html, $allowed_html );
2273 $allowed_html['a']['data-gallery'] = true;
2274 $allowed_html['a']['download'] = true;
2275 $allowed_html['blockquote']['class'] = true;
2276 $allowed_html['blockquote']['data-width'] = true;
2277 $allowed_html['blockquote']['data-userid'] = true;
2278 $allowed_html['blockquote']['data-postid'] = true;
2279 $allowed_html['p']['lang'] = true;
2280 $allowed_html['p']['dir'] = true;
2281 $allowed_html['pre']['contenteditable'] = true;
2282 // Allow <figure> across core for attachments and GIFs
2283 $allowed_html['figure'] = [
2284 'contenteditable' => true,
2285 'class' => true,
2286 'style' => true,
2287 'data-*' => true,
2288 ];
2289 if( ! wpfval( $allowed_html, 'iframe' ) && class_exists( 'wpForoEmbeds' ) ) {
2290 $allowed_html['iframe'] = [ 'width' => true, 'height' => true, 'src' => true, 'frameborder' => true, 'allowfullscreen' => true ];
2291 }
2292 $allowed_html = apply_filters( 'wpforo_kses_allowed_html', $allowed_html );
2293 }
2294
2295 return wp_kses( $string, $allowed_html );
2296 }
2297
2298 function wpforo_deep_merge( $default, $current = [] ) {
2299 foreach( $default as $k => $v ) {
2300 if( ! empty( $v ) && is_array( $v ) ) {
2301 foreach( $v as $kk => $vv ) {
2302 if( ! empty( $vv ) && is_array( $vv ) ) {
2303 foreach( $vv as $kkk => $vvv ) {
2304 if( ! empty( $vvv ) && is_array( $vvv ) ) {
2305 foreach( $vvv as $kkkk => $vvvv ) {
2306 if( ! empty( $vvv ) && is_array( $vvv ) ) {
2307 //Stop on 5th level
2308 } else {
2309 if( isset( $current[ $k ][ $kk ][ $kkk ][ $kkkk ] ) ) $default[ $k ][ $kk ][ $kkk ][ $kkkk ] = $current[ $k ][ $kk ][ $kkk ][ $kkkk ];
2310 }
2311 }
2312 } else {
2313 if( isset( $current[ $k ][ $kk ][ $kkk ] ) ) $default[ $k ][ $kk ][ $kkk ] = $current[ $k ][ $kk ][ $kkk ];
2314 }
2315 }
2316 } else {
2317 if( isset( $current[ $k ][ $kk ] ) ) $default[ $k ][ $kk ] = $current[ $k ][ $kk ];
2318 }
2319 }
2320 } else {
2321 if( isset( $current[ $k ] ) ) $default[ $k ] = $current[ $k ];
2322 }
2323 }
2324
2325 return $default;
2326 }
2327
2328 function wpforo_is_image( $e ) {
2329 return (bool) preg_match( '#^(jpe?g|png|gif|bmp|webp|tiff?)$#i', (string) $e );
2330 }
2331
2332 function wpforo_is_video( $e ) {
2333 return (bool) preg_match( '#^(mp4|webm|ogg)$#i', (string) $e );
2334 }
2335
2336 function wpforo_is_audio( $e ) {
2337 return (bool) preg_match( '#^(mp3|wav|ogg)$#i', (string) $e );
2338 }
2339
2340 /**
2341 * @param string $option
2342 * @param mixed $default
2343 * @param bool $cache
2344 *
2345 * @return mixed
2346 */
2347 function wpforo_get_option( $option, $default = null, $cache = true ) {
2348 if( strpos( (string) $option, 'wpforo_' ) !== 0 ) {
2349 $option = wpforo_prefix( $option );
2350 }
2351
2352 $from_cache = false;
2353 $value = null;
2354
2355 $cache = ( $cache && apply_filters( 'wpforo_options_cache', true ) );
2356
2357 if( $cache ) {
2358 $option_file = WPF()->folders['cache']['dir'] . DIRECTORY_SEPARATOR . 'item' . DIRECTORY_SEPARATOR . 'option' . DIRECTORY_SEPARATOR . ( defined( 'AUTH_KEY' ) ? md5( $option . AUTH_KEY ) : md5(
2359 $option
2360 ) );
2361 $value = maybe_unserialize( wpforo_get_file_content( $option_file ) );
2362 if( ! empty( $value ) ) $from_cache = true;
2363 }
2364
2365 if( ! $from_cache ) $value = get_option( $option );
2366
2367 if( $value === false ) {
2368 $value = $default;
2369 } else {
2370 if( preg_match( '#_(?:general|profiles|rating|tools_misc)$#iu', (string) $option ) ) {
2371 foreach( [ 'title_groupids', 'title_secondary_groupids', 'rating_title_ug', 'rating_badge_ug', 'admin_bar', 'admin_note_groups', 'admin_note_pages' ] as $k ) {
2372 if( wpfkey( $default, $k ) ) $default[ $k ] = [];
2373 }
2374 }
2375 if( preg_match( '#_activity$#iu', (string) $option ) ) {
2376 if( wpfkey( $default, 'enabled_types' ) ) $default['enabled_types'] = [];
2377 }
2378 if( preg_match( '#_ai$#iu', (string) $option ) ) {
2379 foreach( [ 'chatbot_allowed_groups', 'bot_reply_includes' ] as $k ) {
2380 if( wpfkey( $default, $k ) ) $default[ $k ] = [];
2381 }
2382 }
2383 $default = apply_filters( 'wpforo_get_option_default_arg_before_cast', $default, $option );
2384
2385 $value = wpforo_settype( $value, gettype( $default ) );
2386 if( $default && is_array( $default ) && is_array( $value ) ) $value = wpforo_array_args_cast_and_merge( $value, $default );
2387 }
2388
2389 if( $cache && ! $from_cache && ! empty( $value ) ) wpforo_write_file( $option_file, maybe_serialize( $value ) );
2390
2391 return apply_filters( 'wpforo_get_option', $value, $option, $default, $cache );
2392 }
2393
2394 /**
2395 * calls the WordPress core update_option() function
2396 *
2397 * @param string $option Name of the option to update. Expected to not be SQL-escaped.
2398 * @param mixed $value Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
2399 * @param string|bool $autoload Optional. Whether to load the option when WordPress starts up. For existing options,
2400 * `$autoload` can only be updated using `update_option()` if `$value` is also changed.
2401 * Accepts 'yes'|true to enable or 'no'|false to disable. For non-existent options,
2402 * the default value is 'yes'. Default null.
2403 *
2404 * @return bool True if the value was updated, false otherwise.
2405 * @since 2.0.0
2406 */
2407 function wpforo_update_option( $option, $value, $autoload = null ) {
2408 if( strpos( (string) $option, 'wpforo_' ) !== 0 ) {
2409 $option = wpforo_prefix( $option );
2410 }
2411 $ret = update_option( $option, $value, $autoload );
2412 do_action( 'wpforo_update_option', $option, $value, $autoload );
2413 wpforo_clean_cache( 'option' );
2414
2415 return $ret;
2416 }
2417
2418 /**
2419 * calls the WordPress core delete_option() function
2420 *
2421 * @param string $option Name of the option to update. Expected to not be SQL-escaped.
2422 *
2423 * @return bool True if the value was updated, false otherwise.
2424 * @since 2.0.0
2425 */
2426 function wpforo_delete_option( $option ) {
2427 if( strpos( (string) $option, 'wpforo_' ) !== 0 ) {
2428 $option = wpforo_prefix( $option );
2429 }
2430 $ret = delete_option( $option );
2431 do_action( 'wpforo_delete_option', $option );
2432 wpforo_clean_cache( 'option' );
2433
2434 return $ret;
2435 }
2436
2437 /**
2438 * Extract what remains from an unintentionally truncated serialized string
2439 * $data contains your original array (or what remains of it).
2440 *
2441 * @param string The serialized array
2442 */
2443 function wpforo_fixSerializedArray( $serialized ) {
2444 $tmp = preg_replace( '/^a:\d+:\{/', '', (string) $serialized );
2445
2446 return wpforo_fixSerializedArray_R( $tmp );
2447 }
2448
2449 /**
2450 * The recursive function that does all the heavy lifing. Do not call directly.
2451 *
2452 * @param string The broken serialzized array
2453 *
2454 * @return array Returns the repaired string
2455 */
2456 function wpforo_fixSerializedArray_R( &$broken ) {
2457 $data = [];
2458 $index = null;
2459 $len = strlen( (string) $broken );
2460 $i = 0;
2461 while( strlen( (string) $broken ) ) {
2462 $i ++;
2463 if( $i > $len ) {
2464 break;
2465 }
2466 if( substr( (string) $broken, 0, 1 ) == '}' ) {
2467 $broken = substr( (string) $broken, 1 );
2468
2469 return $data;
2470 } else {
2471 $bite = substr( (string) $broken, 0, 2 );
2472 switch( $bite ) {
2473 case 's:':
2474 $re = '/^s:\d+:"([^\"]*)";/';
2475 if( preg_match( $re, (string) $broken, $m ) ) {
2476 if( $index === null ) {
2477 $index = $m[1];
2478 } else {
2479 $data[ $index ] = $m[1];
2480 $index = null;
2481 }
2482 $broken = preg_replace( $re, '', (string) $broken );
2483 }
2484 break;
2485 case 'i:':
2486 $re = '/^i:(\d+);/';
2487 if( preg_match( $re, (string) $broken, $m ) ) {
2488 if( $index === null ) {
2489 $index = (int) $m[1];
2490 } else {
2491 $data[ $index ] = (int) $m[1];
2492 $index = null;
2493 }
2494 $broken = preg_replace( $re, '', (string) $broken );
2495 }
2496 break;
2497 case 'b:':
2498 $re = '/^b:[01];/';
2499 if( preg_match( $re, (string) $broken, $m ) ) {
2500 $data[ $index ] = (bool) $m[1];
2501 $index = null;
2502 $broken = preg_replace( $re, '', (string) $broken );
2503 }
2504 break;
2505 case 'a:':
2506 $re = '/^a:\d+:\{/';
2507 if( preg_match( $re, (string) $broken, $m ) ) {
2508 $broken = preg_replace( '/^a:\d+:\{/', '', (string) $broken );
2509 $data[ $index ] = wpforo_fixSerializedArray_R( $broken );
2510 $index = null;
2511 }
2512 break;
2513 case 'N;':
2514 $broken = substr( (string) $broken, 2 );
2515 $data[ $index ] = null;
2516 $index = null;
2517 break;
2518 }
2519 }
2520 }
2521
2522 return $data;
2523 }
2524
2525 function wpforo_insert_to_media_library( $attach_path, $title = '' ) {
2526 if( wpforo_setting( 'posting', 'attachs_to_medialib' ) ) {
2527 if( ! $attach_path ) return 0;
2528 $attach_fname = basename( (string) $attach_path );
2529 if( ! $title ) $title = $attach_fname;
2530 require_once( ABSPATH . 'wp-admin/includes/media.php' );
2531 require_once( ABSPATH . 'wp-admin/includes/file.php' );
2532 require_once( ABSPATH . 'wp-admin/includes/image.php' );
2533 $filetype = wp_check_filetype( $attach_fname, null );
2534 $attachment = [ 'guid' => $attach_path, 'post_mime_type' => $filetype['type'], 'post_title' => $title, 'post_content' => '', 'post_status' => 'inherit' ];
2535 $attach_id = wp_insert_attachment( $attachment, $attach_path );
2536 add_filter( 'intermediate_image_sizes', 'wpforo_attachment_sizes' );
2537 $attach_data = wp_generate_attachment_metadata( $attach_id, $attach_path );
2538 wp_update_attachment_metadata( $attach_id, $attach_data );
2539 remove_filter( 'intermediate_image_sizes', 'wpforo_attachment_sizes' );
2540
2541 return $attach_id;
2542 }
2543
2544 return 0;
2545 }
2546
2547 function wpforo_attachment_sizes( $sizes ) {
2548 return [ 'thumbnail' ];
2549 }
2550
2551 #################################################################################
2552 /**
2553 * Cleans forum cache
2554 *
2555 * @param string Item View / Template (e.g.: 'forum', 'topic', 'post', 'user', 'widget', etc...)
2556 * @param integer Item ID (e.g.: $topicid or $postid) | (!) ID is 0 on dome actions (e.g.: delete actions)
2557 * @param array Item data as array
2558 *
2559 * @since 1.2.1
2560 *
2561 */
2562 function wpforo_clean_cache( $template = 'all', $id = 0, $item = [] ) {
2563 if( is_null( WPF()->board ) ) return;
2564 do_action( 'wpforo_clean_cache_start', $id, $template );
2565
2566 if( ! $pageid = WPF()->board->get_current( 'pageid' ) ) $pageid = wpforo_wp_url_to_postid( $_SERVER['REQUEST_URI'] );
2567 if( $pageid ) clean_post_cache( $pageid );
2568
2569 do_action( 'wpforo_clean_cache', $id, $template );
2570 WPF()->cache->clean( $id, $template, $item );
2571 if( $template !== 'option' ) WPF()->statistic_cache_clean();
2572 do_action( 'wpforo_clean_cache_end', $id, $template );
2573 }
2574
2575 function wpforo_is_owner( $userid, $email = '' ) {
2576 return wpforo_is_users_same( [ 'userid' => $userid, 'user_email' => $email ] );
2577 }
2578
2579 function wpforo_is_users_same( $user1, $user2 = null ) {
2580 if( is_null( $user2 ) ) {
2581 $user2 = WPF()->current_user;
2582 } elseif( is_scalar( $user2 ) ) {
2583 if( is_numeric( $user2 ) ) {
2584 $user2 = [ 'userid' => wpforo_bigintval( $user2 ), 'user_email' => wpforo_member( $user2, 'user_email' ) ];
2585 } else {
2586 $user2 = [ 'userid' => 0, 'user_email' => sanitize_email( $user2 ) ];
2587 }
2588 }
2589
2590 if( is_scalar( $user1 ) ) {
2591 if( is_numeric( $user1 ) ) {
2592 $user1 = [ 'userid' => wpforo_bigintval( $user1 ), 'user_email' => wpforo_member( $user1, 'user_email' ) ];
2593 } else {
2594 $user1 = [ 'userid' => 0, 'user_email' => sanitize_email( $user1 ) ];
2595 }
2596 }
2597
2598 if( wpforo_bigintval( wpfval( $user2, 'userid' ) ) && ( $userid = wpforo_bigintval( wpfval( $user1, 'userid' ) ) ) ) {
2599 return $userid === $user2['userid'];
2600 } elseif( wpfval( $user2, 'user_email' ) && ( $user_email = sanitize_email( wpfval( $user1, 'user_email' ) ) ) ) {
2601 return $user_email === $user2['user_email'];
2602 }
2603
2604 return false;
2605 }
2606
2607 function wpforo_dashboard_is_owner() {
2608 return ( preg_match( '#/profile\.php$#iu', $_SERVER['REQUEST_URI'] ) || ( preg_match( '#/user-edit\.php#iu', $_SERVER['REQUEST_URI'] ) && (int) wpfval( $_GET, 'user_id' ) === WPF(
2609 )->current_userid ) );
2610 }
2611
2612 /**
2613 * @param array $user wpforo user array
2614 * @param bool $echo
2615 *
2616 * @return string
2617 */
2618 function wpforo_user_dname( $user, $echo = false ) {
2619 $display_name = trim( (string) wpfval( $user, 'display_name' ) );
2620 $user_nicename = trim( (string) wpfval( $user, 'user_nicename' ) );
2621 $dname = esc_html( $display_name ) ?: ( esc_html( urldecode( $user_nicename ) ) ?: wpforo_phrase( 'Anonymous', false ) );
2622 $dname = apply_filters( 'wpforo_user_display_name', $dname, $user );
2623 if( $echo ) echo $dname;
2624
2625 return $dname;
2626 }
2627
2628 function wpforo_strlen( $string ) {
2629 if( ! $string ) return 0;
2630 if( function_exists( 'mb_strlen' ) ) {
2631 return mb_strlen( (string) $string );
2632 } else {
2633 return strlen( (string) $string );
2634 }
2635 }
2636
2637 function wpforo_string2array( $string, $regexp = '' ) {
2638 if( ! $regexp ) $regexp = '#' . preg_quote( PHP_EOL ) . '#isu';
2639 $array = preg_split( $regexp, $string );
2640
2641 return array_filter( $array );
2642 }
2643
2644 function wpforo_array_ordered_intersect_key( $array1, $array2 ) {
2645 $new_array = [];
2646 foreach( $array2 as $key => $value ) if( wpfkey( $array1, $key ) ) $new_array[ $key ] = $array1[ $key ];
2647
2648 return $new_array;
2649 }
2650
2651 function wpforo_get_upload_dir_folders() {
2652 return (array) array_diff( scandir( WPF()->folders['upload']['dir'] ), [ '.', '..' ] );
2653 }
2654
2655 function wpforo_fix_upload_dir( $upload_dir ) {
2656 $folders = wpforo_ram_get( 'wpforo_get_upload_dir_folders' );
2657 $folders = array_map( 'preg_quote', $folders );
2658 if( $folders && preg_match( '#[/\\\]wpforo(?:_\d+)?[/\\\](?:' . implode( '|', $folders ) . ')[/\\\].+?$#iu', (string) $upload_dir, $match ) ) {
2659 $upload_dir = wpforo_fix_dir_sep( WPF()->folders['wp_upload']['dir'] . $match[0] );
2660 $upload_dir = urldecode( (string) $upload_dir );
2661 } else {
2662 // Security: Return empty string for paths outside wpforo uploads to prevent arbitrary file operations
2663 $upload_dir = '';
2664 }
2665
2666 return $upload_dir;
2667 }
2668
2669 function wpforo_fix_upload_url( $upload_url ) {
2670 $folders = wpforo_ram_get( 'wpforo_get_upload_dir_folders' );
2671 $folders = array_map( 'preg_quote', $folders );
2672 if( $folders && preg_match( '#[/\\\]wpforo(?:_\d+)?[/\\\](?:' . implode( '|', $folders ) . ')[/\\\].+?$#iu', (string) $upload_url, $match ) ) {
2673 $upload_url = wpforo_fix_url_sep( WPF()->folders['wp_upload']['url'] . $match[0] );
2674 }
2675
2676 return $upload_url;
2677 }
2678
2679 function wpforo_xcopy( $source, $dest ) {
2680 // Check for symlinks
2681 if( is_link( $source ) ) {
2682 return symlink( readlink( $source ), $dest );
2683 }
2684
2685 // Simple copy for a file
2686 if( is_file( $source ) ) {
2687 return copy( $source, $dest );
2688 }
2689
2690 // Make destination directory
2691 if( ! is_dir( $dest ) ) {
2692 wp_mkdir_p( $dest );
2693 }
2694
2695 // Loop through the folder
2696 $dir = dir( $source );
2697 while( false !== $entry = $dir->read() ) {
2698 // Skip pointers
2699 if( $entry === '.' || $entry === '..' ) {
2700 continue;
2701 }
2702
2703 // Deep copy directories
2704 wpforo_xcopy( rtrim( (string) $source, '/' ) . "/$entry", rtrim( (string) $dest, '/' ) . "/$entry" );
2705 }
2706
2707 // Clean up
2708 $dir->close();
2709
2710 return true;
2711 }
2712
2713 function wpforo_printf_array( $format, $arr ) {
2714 return call_user_func_array( 'printf', array_merge( (array) $format, (array) $arr ) );
2715 }
2716
2717 function wpforo_sprintf_array( $format, $arr ) {
2718 return call_user_func_array( 'sprintf', array_merge( (array) $format, (array) $arr ) );
2719 }
2720
2721 function wpforo_avatar_url( $avatar_html ) {
2722 if( preg_match( '#src=[\'"]([^\'"]+?)[\'"]#iu', (string) $avatar_html, $matches ) ) {
2723 return $matches[1];
2724 }
2725
2726 return '';
2727 }
2728
2729 /**
2730 * @param string $content
2731 * @param bool $first
2732 * @param string $type
2733 *
2734 * @return string|array
2735 */
2736 function wpforo_find_image_urls( $content, $first = true, $type = 'general' ) {
2737 $images = [];
2738 $content = trim( (string) $content );
2739
2740 if( $content ) {
2741 if( preg_match_all( '#<img[^<>]*?src=[\'\"]([^\'\"]+\.(?:jpe?g|png|gif|bmp|webp|svg|tiff))[\'\"][^<>]*?>#iu', (string) $content, $matches, PREG_SET_ORDER ) ) {
2742 foreach( $matches as $match ) {
2743 if( preg_match( '#class=[\'\"]wpfem[^\'\"]*[\'\"][^<>]*?data-code=#isu', $match[0] ) ) continue;
2744 if( strpos( (string) $match[1], 'http' ) === 0 ) {
2745 $images[] = $match[1];
2746 } else {
2747 $images[] = 'http' . ( is_ssl() ? 's' : '' ) . ':' . $match[1];
2748 }
2749 }
2750 } elseif( preg_match_all( '#https?://[^\r\n\t\s\'\"<>]+?\.(?:jpe?g|png|gif|bmp|webp|svg|tiff)#iu', (string) $content, $matches, PREG_SET_ORDER ) ) {
2751 foreach( $matches as $match ) {
2752 $images[] = $match[0];
2753 }
2754 } elseif( preg_match_all( '#//[^\r\n\t\s\'\"<>]+?\.(?:jpe?g|png|gif|bmp|webp|svg|tiff)#iu', (string) $content, $matches, PREG_SET_ORDER ) ) {
2755 foreach( $matches as $match ) {
2756 $images[] = 'http' . ( is_ssl() ? 's' : '' ) . ':' . $match[0];
2757 }
2758 }
2759 }
2760
2761 if( $first && $images ) $images = wpfval( $images, 0 );
2762
2763 return apply_filters( 'wpforo_find_image_url', $images, $type, $first );
2764 }
2765
2766 function wpforo_is_json( $string ) {
2767 if( is_scalar( $string ) ) {
2768 json_decode( $string );
2769
2770 return json_last_error() === JSON_ERROR_NONE;
2771 }
2772
2773 return false;
2774 }
2775
2776 function wpforo_ajax_response( $message ) {
2777 wp_send_json( $message );
2778 die();
2779 }
2780
2781 function wpforo_unique_username( $username ) {
2782 static $i;
2783 if( ! $username ) $username = 'user_' . uniqid();
2784 if( strpos( (string) $username, '@' ) !== false ) {
2785 $parts = explode( "@", $username );
2786 if( ! empty( $parts ) && isset( $parts[0] ) && $parts[0] ) {
2787 $username = $parts[0];
2788 } else {
2789 $username = str_replace( '@', '', $username );
2790 }
2791 }
2792 if( null === $i ) {
2793 $i = 1;
2794 } else {
2795 $i ++;
2796 }
2797 if( ! username_exists( $username ) ) {
2798 return $username;
2799 }
2800 $new_username = sprintf( '%s-%s', $username, $i );
2801 if( ! username_exists( $new_username ) ) {
2802 return $new_username;
2803 } else {
2804 return call_user_func( __FUNCTION__, $username );
2805 }
2806 }
2807
2808 function wpforo_is_session_started() {
2809 if( php_sapi_name() !== 'cli' ) {
2810 if( version_compare( phpversion(), '5.4.0', '>=' ) ) {
2811 return session_status() === PHP_SESSION_ACTIVE ? true : false;
2812 } else {
2813 return session_id() === '' ? false : true;
2814 }
2815 }
2816
2817 return false;
2818 }
2819
2820 function wpforo_current_guest( $email ) {
2821 $guest = WPF()->member->get_guest_cookies();
2822 if( ! wpfval( $guest, 'email' ) || ! $guest['email'] ) return false;
2823 if( $email == $guest['email'] ) {
2824 return true;
2825 } else {
2826 return false;
2827 }
2828 }
2829
2830 /**
2831 * Add a post to the guest's signed ownership cookie.
2832 * Called when a guest creates a new post or topic.
2833 *
2834 * @param int $postid The post ID to add to ownership
2835 */
2836 function wpforo_add_guest_ownership( $postid ) {
2837 // Respect the forum cookie policy, like every other wpForo cookie writer.
2838 if( ! wpforo_setting( 'legal', 'cookies' ) ) return;
2839
2840 $postid = (int) $postid;
2841 if( ! $postid ) return;
2842
2843 $owned = wpforo_get_guest_owned_posts();
2844 $owned[] = $postid;
2845
2846 // Keep the newest N ids only, so the cookie can't grow past browser limits.
2847 $max = (int) apply_filters( 'wpforo_guest_ownership_max_posts', 100 );
2848 $owned = array_values( array_unique( $owned ) );
2849 if( $max > 0 && count( $owned ) > $max ) $owned = array_slice( $owned, - $max );
2850
2851 $data = wp_json_encode( $owned );
2852 if( ! is_string( $data ) ) return;
2853
2854 $signature = hash_hmac( 'sha256', $data, wp_salt( 'auth' ) );
2855 $cookie_value = base64_encode( $data ) . '.' . $signature;
2856
2857 $expire = time() + ( 30 * DAY_IN_SECONDS );
2858 $secure = is_ssl() && 'https' === parse_url( get_option( 'home' ), PHP_URL_SCHEME );
2859 $path = ( COOKIEPATH != SITECOOKIEPATH ) ? SITECOOKIEPATH : COOKIEPATH;
2860
2861 // Suppressed: output may already have started on some themes/hooks, and a
2862 // failed cookie must degrade to "cannot edit", never to a PHP warning.
2863 @setcookie( 'wpforo_guest_ownership', $cookie_value, $expire, $path, COOKIE_DOMAIN, $secure, true );
2864
2865 // Make it readable within the same request.
2866 $_COOKIE['wpforo_guest_ownership'] = $cookie_value;
2867 }
2868
2869 /**
2870 * Get list of post IDs this guest owns (verified via signature).
2871 * Returns empty array if cookie is missing, corrupted, or signature invalid.
2872 *
2873 * @return array List of post IDs
2874 */
2875 function wpforo_get_guest_owned_posts() {
2876 $cookie = isset( $_COOKIE['wpforo_guest_ownership'] ) ? $_COOKIE['wpforo_guest_ownership'] : '';
2877
2878 // The cookie is fully attacker-controlled: it can arrive as an array
2879 // (wpforo_guest_ownership[]=x), which would fatal on strpos() in PHP 8.
2880 if( ! is_string( $cookie ) || $cookie === '' ) return [];
2881
2882 $parts = explode( '.', $cookie, 2 );
2883 if( count( $parts ) !== 2 ) return [];
2884
2885 list( $data_b64, $signature ) = $parts;
2886 $data = base64_decode( $data_b64, true );
2887 if( ! is_string( $data ) || $data === '' ) return [];
2888
2889 // Verify HMAC signature - prevents forgery
2890 $expected = hash_hmac( 'sha256', $data, wp_salt( 'auth' ) );
2891 if( ! hash_equals( $expected, $signature ) ) return [];
2892
2893 $owned = json_decode( $data, true );
2894 if( ! is_array( $owned ) ) return [];
2895
2896 return array_values( array_filter( array_map( 'intval', $owned ) ) );
2897 }
2898
2899 /**
2900 * Check if the current guest owns a specific post.
2901 * Uses cryptographically signed cookie - cannot be forged.
2902 *
2903 * @param int $postid The post ID to check
2904 * @return bool True if guest owns this post
2905 */
2906 function wpforo_guest_owns_post( $postid ) {
2907 $owned = wpforo_get_guest_owned_posts();
2908 return in_array( (int) $postid, $owned, true );
2909 }
2910
2911 function wpforo_extra_html_parser( $extra_html = '', $allowed_html = [] ) {
2912 if( $extra_html ) {
2913 $extra_html = explode( ',', $extra_html );
2914 $extra_html = array_filter( $extra_html );
2915 if( ! empty( $extra_html ) ) {
2916 foreach( $extra_html as $html ) {
2917 $html = trim( (string) $html );
2918 if( preg_match( '|([^()]+)\((.+)\)|', $html, $item ) ) {
2919 if( wpfval( $item, 1 ) && wpfval( $item, 2 ) ) {
2920 $attrs = explode( ' ', $item[2] );
2921 $attrs = array_map( 'trim', $attrs );
2922 foreach( $attrs as $attr ) {
2923 $allowed_html[ $item[1] ][ $attr ] = [];
2924 }
2925 }
2926 } else {
2927 $allowed_html[ $html ] = [];
2928 }
2929 }
2930 }
2931 }
2932
2933 return $allowed_html;
2934 }
2935
2936 function wpforo_clear_array( $array, $clear = [], $by = 'value' ) {
2937 if( is_array( $clear ) && ! empty( $clear ) ) {
2938 foreach( $clear as $ext ) {
2939 if( $by == 'value' ) {
2940 if( ( $key = array_search( $ext, $array ) ) !== false ) {
2941 unset( $array[ $key ] );
2942 }
2943 } elseif( $by == 'key' ) {
2944 if( wpfkey( $array, $ext ) ) unset( $array[ $ext ] );
2945 }
2946 }
2947 } elseif( is_string( $clear ) || is_numeric( $clear ) ) {
2948 if( wpfval( $array, $clear ) ) unset( $array[ $clear ] );
2949 }
2950
2951 return $array;
2952 }
2953
2954 function wpforo_key( $array = [], $value = '', $type = 'default' ) {
2955 $keys = [];
2956 if( is_array( $array ) && ! empty( $array ) ) {
2957 foreach( $array as $k => $v ) {
2958 if( $v === $value ) {
2959 $keys[] = $k;
2960 }
2961 }
2962 }
2963 if( $type === 'sort' ) {
2964 sort( $keys );
2965
2966 return $keys;
2967 } else {
2968 return $keys;
2969 }
2970 }
2971
2972 function wpforo_unslashe( $data ) {
2973 $data = is_array( $data ) ? array_map( 'wpforo_unslashe', $data ) : stripslashes( (string) $data );
2974
2975 return $data;
2976 }
2977
2978 function wpforo_encode( $data ) {
2979 $data = is_array( $data ) ? array_map( 'wpforo_encode', $data ) : htmlspecialchars( $data, ENT_QUOTES );
2980
2981 return $data;
2982 }
2983
2984 function wpforo_decode( $data ) {
2985 $data = is_array( $data ) ? array_map( 'wpforo_decode', $data ) : htmlspecialchars_decode( $data, ENT_QUOTES );
2986
2987 return $data;
2988 }
2989
2990 /**
2991 * Build a REGEXP literal for searching inside a wp_json_encode() stored column.
2992 *
2993 * The haystack (profiles.fields, postmeta.metavalue) is written with wp_json_encode(),
2994 * which escapes forward slashes ("S/4HANA" is stored as S\/4HANA) and non-ASCII
2995 * characters (Ľ), so the needle must be encoded the same way or it can never match.
2996 * Callers must still wrap the result in esc_sql() before putting it in a query.
2997 *
2998 * @param string $value Raw needle.
2999 *
3000 * @return string Regex-literal needle, without SQL escaping.
3001 */
3002 function wpforo_json_regexp_needle( $value ): string {
3003 $value = (string) $value;
3004
3005 // esc_sql() cannot reliably escape invalid UTF-8 on a utf8mb4 connection and
3006 // wp_json_encode() returns false for it, so strip it before encoding.
3007 $clean = wp_check_invalid_utf8( $value, true );
3008 $json = ( ! is_string( $clean ) || $clean === '' ) ? false : wp_json_encode( $clean );
3009
3010 // Never return an empty needle for a non-empty search term: in a substring
3011 // pattern that would match every row. \u0000 cannot occur in stored field data.
3012 if( ! is_string( $json ) || strlen( $json ) < 2 ) return ( $value === '' ) ? '' : preg_quote( '\u0000' );
3013
3014 return preg_quote( substr( $json, 1, -1 ) );
3015 }
3016
3017 function wpforo_trim( $data ) {
3018 $data = is_array( $data ) ? array_map( 'wpforo_trim', $data ) : trim( (string) $data );
3019
3020 return $data;
3021 }
3022
3023 function wpforo_sanitize_int( $data ) {
3024 $data = is_array( $data ) ? array_map( 'wpforo_sanitize_int', $data ) : intval( $data );
3025
3026 return $data;
3027 }
3028
3029 function wpforo_sanitize_text( $data ) {
3030 $data = is_array( $data ) ? array_map( 'wpforo_sanitize_text', $data ) : sanitize_text_field( $data );
3031
3032 return $data;
3033 }
3034
3035 /**
3036 * Sanitize orderby parameter for SQL queries.
3037 * Validates against a whitelist of allowed column names to prevent SQL injection.
3038 *
3039 * @param string $orderby The orderby value to sanitize
3040 * @param string $context The context: 'topics', 'posts', 'members', or 'search'
3041 * @param string $default Default value if validation fails
3042 * @return string Sanitized orderby value
3043 */
3044 function wpforo_sanitize_orderby( $orderby, $context = 'topics', $default = '' ) {
3045 $orderby = sanitize_text_field( $orderby );
3046
3047 // Define allowed columns for each context
3048 $allowed = [
3049 'topics' => [
3050 'topicid', 'forumid', 'userid', 'title', 'slug', 'created', 'modified',
3051 'views', 'posts', 'type', 'status', 'private', 'closed', 'solved',
3052 'has_attach', 'first_postid', 'last_postid', 'pollid', 'prefix'
3053 ],
3054 'posts' => [
3055 'postid', 'forumid', 'topicid', 'userid', 'title', 'created', 'modified',
3056 'status', 'private', 'is_answer', 'is_first_post', 'votes', 'root', 'parentid'
3057 ],
3058 'members' => [
3059 'userid', 'posts', 'questions', 'answers', 'comments', 'reactions', 'points',
3060 'online_time', 'registered', 'display_name', 'user_registered'
3061 ],
3062 'search' => [
3063 'relevancy', 'date', 'user', 'forum', 'created', 'modified'
3064 ],
3065 ];
3066
3067 // Get the whitelist for this context
3068 $whitelist = isset( $allowed[$context] ) ? $allowed[$context] : [];
3069
3070 // Also allow common aliases
3071 $whitelist = array_merge( $whitelist, [ 'id', 'date', 'name' ] );
3072
3073 // Check if orderby is in the whitelist (case-insensitive)
3074 $orderby_lower = strtolower( $orderby );
3075 foreach( $whitelist as $allowed_col ) {
3076 if( strtolower( $allowed_col ) === $orderby_lower ) {
3077 return $allowed_col; // Return the properly cased version
3078 }
3079 }
3080
3081 // If not in whitelist, return the default
3082 return $default;
3083 }
3084
3085 if( ! function_exists( 'sanitize_textarea_field' ) && ! function_exists( '_sanitize_text_fields' ) ) {
3086 function sanitize_textarea_field( $str ) {
3087 $filtered = _sanitize_text_fields( $str, true );
3088
3089 return apply_filters( 'sanitize_textarea_field', $filtered, $str );
3090 }
3091
3092 function _sanitize_text_fields( $str, $keep_newlines = false ) {
3093 $filtered = wp_check_invalid_utf8( $str );
3094 if( strpos( (string) $filtered, '<' ) !== false ) {
3095 $filtered = wp_pre_kses_less_than( $filtered );
3096 $filtered = wp_strip_all_tags( $filtered, false );
3097 $filtered = str_replace( "<\n", "&lt;\n", $filtered );
3098 }
3099 if( ! $keep_newlines ) {
3100 $filtered = preg_replace( '/[\r\n\t ]+/', ' ', (string) $filtered );
3101 }
3102 $filtered = trim( (string) $filtered );
3103 $found = false;
3104 while( preg_match( '/%[a-f0-9]{2}/i', (string) $filtered, $match ) ) {
3105 $filtered = str_replace( $match[0], '', $filtered );
3106 $found = true;
3107 }
3108 if( $found ) {
3109 $filtered = trim( preg_replace( '/ +/', ' ', (string) $filtered ) );
3110 }
3111
3112 return $filtered;
3113 }
3114 }
3115
3116 /**
3117 * @param string $role
3118 *
3119 * @return bool
3120 */
3121 function wpforo_current_user_is( $role ) {
3122 $role = strtolower( (string) $role );
3123
3124 $filter_result = apply_filters( 'wpforo_current_user_is', null, $role );
3125 if( ! is_null( $filter_result ) ) return (bool) $filter_result;
3126
3127 switch( $role ) {
3128 case 'admin':
3129 if( current_user_can( 'activate_plugins' ) ) {
3130 return true;
3131 }
3132 if( current_user_can( 'install_plugins' ) ) {
3133 return true;
3134 }
3135 if( current_user_can( 'create_sites' ) ) {
3136 return true;
3137 }
3138 break;
3139 case 'moderator':
3140 if( WPF()->usergroup->can( 'aum' ) ) {
3141 return true;
3142 }
3143 if( current_user_can( 'moderate_comments' ) ) {
3144 return true;
3145 }
3146 if( current_user_can( 'edit_published_posts' ) ) {
3147 return true;
3148 }
3149 if( current_user_can( 'manage_categories' ) ) {
3150 return true;
3151 }
3152 break;
3153 }
3154
3155 return false;
3156 }
3157
3158 /**
3159 * @param int $userid
3160 * @param string $role
3161 *
3162 * @return bool
3163 */
3164 function wpforo_user_is( $userid, $role ) {
3165 $userid = wpforo_bigintval( $userid );
3166 $role = strtolower( (string) $role );
3167
3168 $filter_result = apply_filters( 'wpforo_user_is', null, $userid, $role );
3169 if( ! is_null( $filter_result ) ) return (bool) $filter_result;
3170
3171 switch( $role ) {
3172 case 'admin':
3173 if( user_can( $userid, 'activate_plugins' ) ) {
3174 return true;
3175 }
3176 if( user_can( $userid, 'install_plugins' ) ) {
3177 return true;
3178 }
3179 if( user_can( $userid, 'create_sites' ) ) {
3180 return true;
3181 }
3182 if( $user = WPF()->member->get_member( $userid ) ) {
3183 if( WPF()->usergroup->can( 'mf', $user['groupids'] ) || WPF()->usergroup->can( 'ms', $user['groupids'] ) ) {
3184 return true;
3185 }
3186 }
3187 break;
3188 case 'moderator':
3189 if( user_can( $userid, 'moderate_comments' ) ) {
3190 return true;
3191 }
3192 if( user_can( $userid, 'edit_published_posts' ) ) {
3193 return true;
3194 }
3195 if( user_can( $userid, 'manage_categories' ) ) {
3196 return true;
3197 }
3198 if( $user = WPF()->member->get_member( $userid ) ) {
3199 if( WPF()->usergroup->can( 'aum', $user['groupids'] ) ) {
3200 return true;
3201 }
3202 }
3203 break;
3204 }
3205
3206 return false;
3207 }
3208
3209 function wpforo_random_colors() {
3210 mt_srand( (int) ( microtime( true ) * 1000000 ) );
3211 $color = '';
3212 while( strlen( (string) $color ) < 6 ) {
3213 $color .= sprintf( "%02X", mt_rand( 0, 255 ) );
3214 }
3215
3216 return '#' . $color;
3217 }
3218
3219 /**
3220 * @param string $dir
3221 *
3222 * @return string
3223 */
3224 function wpforo_fix_dir_sep( string $dir ): string {
3225 return WPF()->fix_dir_sep( $dir );
3226 }
3227
3228 /**
3229 * @param string $url
3230 *
3231 * @return string
3232 */
3233 function wpforo_fix_url_sep( string $url ): string {
3234 return WPF()->fix_url_sep( $url );
3235 }
3236
3237 function wpforo_root_exist() {
3238 $args = [ 'table' => WPF()->tables->posts, 'col' => 'root', 'check' => 'col_exists' ];
3239
3240 return wpforo_db_check( $args );
3241 }
3242
3243 function wpforo_urlencode( $str ): string {
3244 $str = (string) $str;
3245 if( ! preg_match( '#^(\#post-\d+|https?:|s?ftp:)$#iu', (string) $str ) && ! preg_match( '#([?&][^?&/=\r\n]*=?[^?&/=\r\n]*)(?1)*$#iu', (string) $str ) && strpos(
3246 (string) $str,
3247 '~'
3248 ) === false && strpos(
3249 (string) $str,
3250 '*'
3251 ) === false && $str === urldecode(
3252 (string) $str
3253 ) ) {
3254 $str = urlencode( (string) $str );
3255 }
3256
3257 // Use wpforo_url_strtolower() instead of strtolower() to keep the exact values
3258 // of GET variables in pagination buttons. Otherwise, the GET values will differ
3259 // and the field value will be changed on the next page.
3260 // Example: on members search page the selected "field=Test" will be changed to "field=test"
3261 // on the next page. And the select field will loos current selected value.
3262 return $str !== urldecode( (string) $str ) ? wpforo_url_strtolower( (string) $str ) : $str;
3263 }
3264
3265 /**
3266 * Converts the URL to lowercase.
3267 *
3268 * This function converts the URL to lowercase. If the $include_query parameter is set to true,
3269 * the entire URL including the query string is converted to lowercase. Otherwise, only the part of the URL
3270 * before the query string is converted to lowercase.
3271 *
3272 * @param string $url The URL to be converted to lowercase.
3273 * @param bool $include_query Optional. Whether to include the query string in the conversion. Default is false.
3274 *
3275 * @return string The URL converted to lowercase.
3276 */
3277 function wpforo_url_strtolower( $url, $include_query = false ): string {
3278 // If $include_query is true, convert the entire URL to lowercase and return it.
3279 if( $include_query ) return strtolower( $url );
3280 // If the URL does not contain a query string, convert the entire URL to lowercase and return it.
3281 if( strpos( $url, '?' ) === false ) {
3282 $url = strtolower( $url );
3283 } else {
3284 // If the URL contains a query string, split the URL into two parts at the '?' character.
3285 $parts = explode( '?', $url );
3286 // Remove the last element from the $parts array and store it in $query.
3287 $query = array_pop( $parts );
3288 $parts = array_map( 'strtolower', $parts );
3289 $url = implode( '?', $parts ) . '?' . $query;
3290 }
3291
3292 return $url;
3293 }
3294
3295 function wpforo_fix_url( string $url ): string {
3296 $hash = '';
3297 $url = preg_replace_callback( '#(\#[^/]*)$#iu', function( $m ) use ( &$hash ) {
3298 $hash = $m[1];
3299
3300 return '';
3301 }, $url, 1 );
3302
3303 if( preg_match( '#^((?:https?:|s?ftp:)//)([^?&/=\s:]+)((?::\d+)?)([^\r\n]*)#iu', (string) $url, $match ) ) {
3304 $match[2] = wpforo_urlencode( $match[2] );
3305 $url = $match[1] . $match[2] . $match[3] . implode( '/', array_map( 'wpforo_urlencode', explode( '/', $match[4] ) ) );
3306 } else {
3307 $url = implode( '/', array_map( 'wpforo_urlencode', explode( '/', $url ) ) );
3308 }
3309
3310 return $url . $hash;
3311 }
3312
3313 function wpforo_is_domains_equal( $url1, $url2 ) {
3314 $domain1 = strtolower( str_replace( 'www.', '', parse_url( $url1, PHP_URL_HOST ) ) );
3315 $domain2 = strtolower( str_replace( 'www.', '', parse_url( $url2, PHP_URL_HOST ) ) );
3316
3317 return $domain1 === $domain2;
3318 }
3319
3320 function wpforo_is_url_internal( $url, $home_url = null ) {
3321 $url = trim( (string) $url );
3322 if( ! preg_match( '#^(?:https?:)?//#iu', (string) $url ) ) return true;
3323 if( ! $home_url ) $home_url = home_url();
3324 $home_url = trim( (string) $home_url );
3325 $url = preg_replace( '#^(https?://)?(www\.)?#iu', '', (string) $url );
3326 $home_url = preg_replace( [ '#^(https?://)?(www\.)?#iu', '#/?\?.*$#isu', '#index\.php/?#iu' ], '', (string) $home_url );
3327
3328 return strpos( (string) $url, $home_url ) === 0;
3329 }
3330
3331 function wpforo_is_url_external( $url, $home_url = null ) {
3332 return ! wpforo_is_url_internal( $url, $home_url );
3333 }
3334
3335 function wpforo_settype( $var, $type ) {
3336 $var_type = strtolower( gettype( $var ) );
3337 $type = strtolower( (string) $type );
3338 $allowed_types = [ 'bool', 'boolean', 'int', 'integer', 'double', 'real', 'float', 'string', 'array', 'object', 'null' ];
3339 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' ]
3340 ) ) && ! ( $var_type === 'array' && $type === 'string' ) ) {
3341 settype( $var, $type );
3342 }
3343
3344 return $var;
3345 }
3346
3347 function wpforo_array_args_cast( $array, $type ) {
3348 return array_map( function( $var ) use ( $type ) {
3349 return wpforo_settype( $var, $type );
3350 }, $array );
3351 }
3352
3353 function wpforo_array_args_cast_and_merge( $array, $default ) {
3354 $array = (array) $array;
3355 $default = (array) $default;
3356 foreach( $array as $key => $value ) {
3357 if( array_key_exists( $key, $default ) ) {
3358 $array[ $key ] = wpforo_settype( $value, gettype( $default[ $key ] ) );
3359 if( is_array( $array[ $key ] ) ) {
3360 $array[ $key ] = wpforo_array_args_cast_and_merge(
3361 $array[ $key ],
3362 $default[ $key ]
3363 );
3364 } #### do not open this comment until you have checkboxes on options settings pages
3365 }
3366 }
3367 $array += $default;
3368
3369 return $array;
3370 }
3371
3372 function wpforo_db_data_format( $var ) {
3373 switch( gettype( $var ) ) {
3374 case 'bool':
3375 case 'boolean':
3376 case 'int':
3377 case 'integer':
3378 return '%d';
3379 case 'double':
3380 case 'real':
3381 case 'float':
3382 return '%f';
3383 default:
3384 return '%s';
3385 }
3386 }
3387
3388 /**
3389 * @param int $seconds execution time in seconds , by default 0 (unlimited)
3390 */
3391 function wpforo_set_max_execution_time( $seconds = 0 ) {
3392 if( function_exists( 'set_time_limit' ) ) @set_time_limit( $seconds );
3393 if( function_exists( 'ini_set' ) ) @ini_set( 'max_execution_time', $seconds );
3394 }
3395
3396 /**
3397 * @param string $pattern
3398 * @param array $array
3399 *
3400 * @return array
3401 */
3402 function wpforo_preg_grep_recursive( $pattern, $array ) {
3403 $m = [];
3404 if( is_array( $array ) ) {
3405 foreach( $array as $key => $value ) {
3406 if( is_string( $value ) || is_numeric( $value ) ) {
3407 if( preg_match( $pattern, (string) $value ) ) $m[ $key ] = $value;
3408 } elseif( is_array( $value = wpforo_settype( $value, 'array' ) ) ) {
3409 if( $_m = wpforo_preg_grep_recursive( $pattern, $value ) ) $m[ $key ] = $_m;
3410 }
3411 }
3412 }
3413
3414 return $m;
3415 }
3416
3417 function wpforo_send_new_user_notifications( $userid, $notify = 'both' ) {
3418 if( wpforo_setting( 'email', 'disable_new_user_admin_notification' ) ) {
3419 if( $notify !== 'admin' ) wp_send_new_user_notifications( $userid, 'user' );
3420 } else {
3421 wp_send_new_user_notifications( $userid, $notify );
3422 }
3423 }
3424
3425 function wpforo_get_callbacks_for_action( $hook = '' ) {
3426 global $wp_filter;
3427 if( empty( $hook ) || ! isset( $wp_filter[ $hook ] ) ) return [];
3428
3429 return $wp_filter[ $hook ]->callbacks;
3430 }
3431
3432 /**
3433 * The optimized version of wordpress url_to_postid($url) function
3434 *
3435 * Examine a URL and try to determine the post ID it represents.
3436 *
3437 * Checks are supposedly from the hosted site blog.
3438 *
3439 * @param string $url Permalink to check.
3440 *
3441 * @return int Post ID, or 0 on failure.
3442 * @global WP $wp Current WordPress environment instance.
3443 *
3444 * @since 1.7.4
3445 *
3446 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
3447 */
3448 function _wpforo_wp_url_to_postid( $url ) {
3449 if( strpos( (string) $url, admin_url() ) !== false ) return 0;
3450
3451 $url_host = str_replace( 'www.', '', (string) parse_url( $url, PHP_URL_HOST ) );
3452 $home_url_host = str_replace( 'www.', '', (string) parse_url( home_url(), PHP_URL_HOST ) );
3453
3454 // Bail early if the URL does not belong to this site.
3455 if( $url_host && $url_host !== $home_url_host ) return 0;
3456
3457 // First, check to see if there is a 'p=N' or 'page_id=N' to match against.
3458 if( preg_match( '#[?&](p|page_id|post_id|post|attachment_id)=(\d+)#', (string) $url, $values ) ) {
3459 if( $id = absint( $values[2] ) ) return $id;
3460 }
3461
3462 global $wp_rewrite;
3463 if( $wp_rewrite ) {
3464 // Get rid of the #anchor.
3465 $url_split = explode( '#', $url );
3466 $url = $url_split[0];
3467
3468 // Get rid of URL ?query=string.
3469 $url_split = explode( '?', $url );
3470 $url = $url_split[0];
3471
3472 // Set the correct URL scheme.
3473 $scheme = parse_url( home_url(), PHP_URL_SCHEME );
3474 $url = set_url_scheme( $url, $scheme );
3475
3476 // Add 'www.' if it is absent and should be there.
3477 if( false !== strpos( home_url(), '://www.' ) && false === strpos( $url, '://www.' ) ) {
3478 $url = str_replace( '://', '://www.', $url );
3479 }
3480
3481 // Strip 'www.' if it is present and shouldn't be.
3482 if( false === strpos( home_url(), '://www.' ) ) {
3483 $url = str_replace( '://www.', '://', $url );
3484 }
3485
3486 if( trim( (string) $url, '/' ) === home_url() && 'page' == get_option( 'show_on_front' ) ) {
3487 $page_on_front = get_option( 'page_on_front' );
3488 if( $page_on_front && get_post( $page_on_front ) instanceof WP_Post ) return (int) $page_on_front;
3489 }
3490
3491 // Check to see if we are using rewrite rules.
3492 $rewrite = $wp_rewrite->wp_rewrite_rules();
3493
3494 // Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options.
3495 if( empty( $rewrite ) ) return 0;
3496
3497 // Strip 'index.php/' if we're not using path info permalinks.
3498 if( ! $wp_rewrite->using_index_permalinks() ) {
3499 $url = str_replace( $wp_rewrite->index . '/', '', $url );
3500 }
3501
3502 if( false !== strpos( trailingslashit( (string) $url ), home_url( '/' ) ) ) {
3503 // Chop off http://domain.com/[path].
3504 $url = str_replace( home_url(), '', $url );
3505 } else {
3506 // Chop off /path/to/blog.
3507 $home_path = parse_url( home_url( '/' ) );
3508 $home_path = isset( $home_path['path'] ) ? $home_path['path'] : '';
3509 $url = preg_replace( sprintf( '#^%s#', preg_quote( $home_path ) ), '', trailingslashit( (string) $url ) );
3510 }
3511
3512 // Trim leading and lagging slashes.
3513 $url = trim( (string) $url, '/' );
3514
3515 $request = $url;
3516 $post_type_query_vars = [];
3517
3518 foreach( get_post_types( [], 'objects' ) as $post_type => $t ) {
3519 if( ! empty( $t->query_var ) ) {
3520 $post_type_query_vars[ $t->query_var ] = $post_type;
3521 }
3522 }
3523
3524 // Look for matches.
3525 $request_match = $request;
3526 foreach( (array) $rewrite as $match => $query ) {
3527 if( preg_match( "#^$match#", (string) $request_match, $matches ) ) {
3528
3529 if( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)]/', (string) $query, $varmatch ) ) {
3530 // This is a verbose page match, let's check to be sure about it.
3531 $page = get_page_by_path( $matches[ $varmatch[1] ] );
3532 if( ! $page ) {
3533 continue;
3534 }
3535
3536 $post_status_obj = get_post_status_object( $page->post_status );
3537 if( ! $post_status_obj->public && ! $post_status_obj->protected && ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
3538 continue;
3539 }
3540 }
3541
3542 // Got a match.
3543 // Trim the query of everything up to the '?'.
3544 $query = preg_replace( '!^.+\?!', '', (string) $query );
3545
3546 // Substitute the substring matches into the query.
3547 $query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) );
3548
3549 // Filter out non-public query vars.
3550 global $wp;
3551 parse_str( $query, $query_vars );
3552 $query = [];
3553 foreach( (array) $query_vars as $key => $value ) {
3554 if( in_array( $key, $wp->public_query_vars ) ) {
3555 $query[ $key ] = $value;
3556 if( isset( $post_type_query_vars[ $key ] ) ) {
3557 $query['post_type'] = $post_type_query_vars[ $key ];
3558 $query['name'] = $value;
3559 }
3560 }
3561 }
3562
3563 // Resolve conflicts between posts with numeric slugs and date archive queries.
3564 $query = wp_resolve_numeric_slug_conflicts( $query );
3565
3566 // Do the query.
3567 if( $p = wpforo_bigintval( wpfval( $query, 'p' ) ) ) return $p;
3568 if( $page_id = wpforo_bigintval( wpfval( $query, 'page_id' ) ) ) return $page_id;
3569
3570 $pagename = wpfval( $query, 'pagename' );
3571 $name = wpfval( $query, 'name' );
3572 if( $pagename || $name ) {
3573 if( ! $slug = $pagename ) $slug = $name;
3574 $sql = "SELECT `ID` FROM `" . WPF()->db->posts . "`
3575 WHERE `post_status` = 'publish'
3576 AND `post_name` = %s";
3577 $post_types = (array) apply_filters( 'wpforo_wp_url_to_postid_post_types', [ 'page', 'post' ] );
3578 if( $post_types ) $sql .= " AND `post_type` IN('" . implode( "','", $post_types ) . "')";
3579 $sql = WPF()->db->prepare( $sql, $slug );
3580 $postid = WPF()->db->get_var( $sql );
3581 if( $postid = wpforo_bigintval( $postid ) ) {
3582 return $postid;
3583 } else {
3584 return 0;
3585 }
3586 } else {
3587 return 0;
3588 }
3589
3590 }
3591 }
3592 }
3593
3594 return 0;
3595 }
3596
3597 function wpforo_wp_url_to_postid( $url ) {
3598 return wpforo_ram_get( '_wpforo_wp_url_to_postid', $url );
3599 }
3600
3601 function wpforo_get_blog_content_types() {
3602 $post_types = get_post_types( [
3603 'public' => true,
3604 'publicly_queryable' => true,
3605 'show_ui' => true,
3606 'capability_type' => 'post',
3607 ] );
3608 $post_types['post'] = 'post';
3609 $post_types['page'] = 'page';
3610 unset( $post_types['attachment'] );
3611 $post_types = array_filter( array_keys( $post_types ) );
3612
3613 return apply_filters( 'wpforo_get_blog_content_types', $post_types );
3614 }
3615
3616 /**
3617 * @param string $css
3618 *
3619 * @return string
3620 */
3621 function wpforo_add_wrapper( $css ) {
3622 $css = preg_replace( '@(#(?:wpforo-wrap|wpfa_dialog|wpfa_dialog_wrap)[\s.#{:>+\[~,])@um', '#wpforo $1', (string) $css );
3623
3624 return preg_replace( '@(#(?:wpforo|wpforo-wrap|wpfa_dialog|wpfa_dialog_wrap)\s*)#wpforo[\s.#{:>+\[~,]@um', '$1', (string) $css );
3625 }
3626
3627 /**
3628 * @param string $css
3629 *
3630 * @return string
3631 */
3632 function wpforo_wrap_fix_in_css( $css ) {
3633 if( ( strpos( (string) $css, '#wpforo-wrap' ) !== false && strpos( (string) $css, '#wpforo #wpforo-wrap' ) === false ) || ( strpos( (string) $css, '#wpfa_dialog' ) !== false && strpos(
3634 (string) $css,
3635 '#wpforo #wpfa_dialog'
3636 ) === false ) ) {
3637 $css = wpforo_add_wrapper( $css );
3638 }
3639
3640 return $css;
3641 }
3642
3643 /**
3644 * @param string $csspath
3645 */
3646 function wpforo_wrap_fix_in_cssfile( $csspath ) {
3647 $csspath = wpforo_fix_dir_sep( $csspath );
3648 if( is_file( $csspath ) ) {
3649 $css = wpforo_get_file_content( $csspath );
3650 $css = wpforo_wrap_fix_in_css( $css );
3651 if( md5( $css ) !== md5_file( $csspath ) ) wpforo_write_file( $csspath, $css );
3652 }
3653 }
3654
3655 function wpforo_wrap_in_all_addons_css() {
3656 $csspaths = [
3657 '/wpforo-ad-manager/assets/css/style.css',
3658 '/wpforo-advanced-attachments/assets/css/style.css',
3659 '/wpforo-cross-posting/assets/css/wpdiscuz-uploader.css',
3660 '/wpforo-cross-posting/assets/css/wpforo-cross-rtl.css',
3661 '/wpforo-cross-posting/assets/css/wpforo-cross.css',
3662 '/wpforo-embeds/assets/css/embed.css',
3663 '/wpforo-emoticons/assets/emoticons.css',
3664 '/wpforo-mycred/css/mycread.css',
3665 '/wpforo-polls/assets/css/poll.css',
3666 '/wpforo-private-messages/assets/css/style-rtl.css',
3667 '/wpforo-private-messages/assets/css/style.css',
3668 '/wpforo-user-custom-fields/assets/css/frontend.css',
3669 ];
3670 foreach( $csspaths as $csspath ) wpforo_wrap_fix_in_cssfile( WP_PLUGIN_DIR . $csspath );
3671
3672 WPF()->dissmissed['addons_css_update'] = 1;
3673 wpforo_update_option( 'dissmissed', WPF()->dissmissed );
3674 }
3675
3676 /*add_action( 'admin_notices', function() {
3677 if( ! (int) wpfval( WPF()->dissmissed, 'addons_css_update' ) ) {
3678 $has_addon = false;
3679 foreach( wpforo_get_addons_info() as $addon ) {
3680 if( class_exists( $addon['class'] ) ) {
3681 $has_addon = true;
3682 break;
3683 }
3684 }
3685 if( ! $has_addon ) return;
3686 $class = 'notice notice-warning';
3687 $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>';
3688 $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>';
3689 printf( '<div class="%1$s"><p>%2$s</p></div>', $class, $message );
3690 }
3691 } );*/
3692
3693 /**
3694 * @param string $url
3695 *
3696 * @return string|null
3697 */
3698 function wpforo_get_topic_slug_from_url( $url = '' ) {
3699 $url = trim( (string) $url );
3700 if( ! $url ) $url = wpforo_get_request_uri();
3701
3702 if( is_wpforo_url( $url ) ) {
3703 if( preg_match( '#/' . preg_quote( wpforo_settings_get_slug( 'postid' ) ) . '/(\d+)/?$#isu', strtok( $url, '?' ), $match ) ) {
3704 $url = WPF()->post->get_url( $match[1] );
3705 }
3706 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 ) ) {
3707 return $match[1];
3708 }
3709 }
3710
3711 return null;
3712 }
3713
3714 function wpforo_clean_folder( $directory ) {
3715 $directory = wpforo_fix_dir_sep( $directory );
3716 $directory_ns = trim( (string) $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR . '*';
3717 $directory_ws = DIRECTORY_SEPARATOR . trim( (string) $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR . '*';
3718 $glob = glob( $directory_ns );
3719 if( empty( $glob ) ) $glob = glob( $directory_ws );
3720 foreach( $glob as $item ) {
3721 if( strpos( (string) $item, 'index.html' ) !== false || strpos( (string) $item, '.htaccess' ) !== false ) continue;
3722 if( ! is_dir( $item ) && file_exists( $item ) ) {
3723 @unlink( $item );
3724 }
3725 }
3726 }
3727
3728 /**
3729 * @param string $basename
3730 *
3731 * @return string
3732 */
3733 function wpforo_fix_table_name( $basename ) {
3734 return WPF()->fix_table_name( $basename );
3735 }
3736
3737 /**
3738 * @param callable $func
3739 * @param mixed ... $_, ... [optional] call_user_func parameters
3740 *
3741 * @return mixed
3742 */
3743 function wpforo_ram_get( $func ) {
3744 return call_user_func_array( [ WPF()->ram_cache, 'call_user_func' ], func_get_args() );
3745 }
3746
3747 function wpforo_prefix( $str = '', $wp_prefix = false ) {
3748 return ( $wp_prefix ? WPF()->blog_prefix : '' ) . WPF()->prefix . $str;
3749 }
3750
3751 function wpforo_prefix_slug( $str = '' ) {
3752 if( $str === 'dashboard' && ! is_wpforo_multiboard() ) $str = 'overview';
3753
3754 return str_replace( '_', '-', wpforo_prefix( $str ) );
3755 }
3756
3757 function wpforo_get_ajax_url() {
3758 return admin_url(
3759 sprintf(
3760 'admin-ajax.php?lang=%1$s&page_id=%2$d&wpforo_boardid=%3$d',
3761 strtok( WPF()->locale, '_' ),
3762 WPF()->board->get_current( 'pageid' ),
3763 WPF()->board->get_current( 'boardid' )
3764 )
3765 );
3766 }
3767
3768 function wpforo_get_query_var_lang() {
3769 if( ! $lang = wpfval( $_GET, 'lang' ) ) $lang = get_query_var( 'lang' );
3770
3771 return $lang;
3772 }
3773
3774 function wpforo_is_db_mysql8() {
3775 return wpforo_ram_get( [ WPF(), 'is_db_mysql8' ] );
3776 }
3777
3778 function wpforo_is_module_enabled( $module_key, $board = [] ) {
3779 return WPF()->board->is_module_enabled( $module_key, $board );
3780 }
3781
3782 function is_wpforo_multiboard() {
3783 return wpforo_ram_get( [ WPF()->board, 'is_multi' ] );
3784 }
3785
3786 function wpforo_get_redirect_to() {
3787 if( ! ( ( $redirect_to = urldecode( (string) wpfval( $_GET, 'redirect_to' ) ) ) && wpforo_is_url_internal( $redirect_to ) ) ) {
3788 $redirect_to = preg_replace( '#/?\?.*$#isu', '', wpforo_get_request_uri() );
3789 }
3790
3791 return $redirect_to;
3792 }
3793
3794 function wpforo_get_redirect_to_url_path( $redirect_to = null ) {
3795 if( is_null( $redirect_to ) ) $redirect_to = wpforo_get_redirect_to();
3796
3797 return $redirect_to ? '?redirect_to=' . urlencode( (string) $redirect_to ) : '';
3798 }
3799
3800 function wpforo_login_url( $redirect_to = null ): string {
3801 if( wpforo_setting( 'authorization', 'login_url' ) ) {
3802 $login_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'login_url' ), '/' );
3803 } else {
3804 $login_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'login' );
3805 }
3806 $login_url = apply_filters( 'wpforo_login_url', $login_url );
3807
3808 return esc_url_raw( $login_url );
3809 }
3810
3811 function wpforo_register_url( $redirect_to = null ): string {
3812 if( wpforo_setting( 'authorization', 'register_url' ) ) {
3813 $register_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'register_url' ), '/' );
3814 } else {
3815 $register_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'register' );
3816 }
3817 $register_url = apply_filters( 'wpforo_register_url', $register_url );
3818
3819 return esc_url_raw( $register_url );
3820 }
3821
3822 function wpforo_logout_url( $redirect_to = null ): string {
3823 $logout_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'logout' );
3824 $logout_url = wp_nonce_url( $logout_url, 'wpforo_logout', '_wpfnonce' );
3825 $logout_url = apply_filters( 'wpforo_logout_url', $logout_url );
3826
3827 return esc_url_raw( $logout_url );
3828 }
3829
3830 function wpforo_lostpassword_url( $redirect_to = null ): string {
3831 if( wpforo_setting( 'authorization', 'lost_password_url' ) ) {
3832 $lostpassword_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'lost_password_url' ), '/' );
3833 } else {
3834 if( wpforo_setting( 'authorization', 'use_our_lostpassword_url' ) ) {
3835 $lostpassword_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'lostpassword' );
3836 } else {
3837 if( is_null( $redirect_to ) ) $redirect_to = wpforo_get_redirect_to();
3838 $lostpassword_url = wp_lostpassword_url( $redirect_to );
3839 }
3840 }
3841 $lostpassword_url = apply_filters( 'wpforo_lostpassword_url', $lostpassword_url );
3842
3843 return esc_url_raw( $lostpassword_url );
3844 }
3845
3846 function wpforo_resetpassword_url( $rp_key, $rp_login ): string {
3847 $rp_key = rawurlencode( rawurldecode( $rp_key ) );
3848 $rp_login = rawurlencode( rawurldecode( $rp_login ) );
3849 $resetpassword_url = wpforo_url( "?wpfaction=resetpassword_form&rp_key=$rp_key&rp_login=$rp_login", 'lostpassword' );
3850 $resetpassword_url = apply_filters( 'wpforo_resetpassword_url', $resetpassword_url );
3851
3852 return esc_url_raw( $resetpassword_url );
3853 }
3854
3855 function wpforo_members_url(): string {
3856 return wpforo_url( '', 'members' );
3857 }
3858
3859 function wpforo_redirect_to() {
3860 $redirect_url = wpforo_home_url();
3861 if( ( $redirect_to = urldecode( (string) wpfval( $_GET, 'redirect_to' ) ) ) && wpforo_is_url_internal( $redirect_to ) && ( ! WPF()->current_userid || ( strpos(
3862 $redirect_to,
3863 wpforo_settings_get_slug( 'login' )
3864 ) === false && strpos(
3865 $redirect_to,
3866 wpforo_settings_get_slug(
3867 'register'
3868 )
3869 ) === false && strpos(
3870 $redirect_to,
3871 wpforo_settings_get_slug(
3872 'lostpassword'
3873 )
3874 ) === false ) ) ) {
3875 $redirect_url = $redirect_to;
3876 }
3877 wp_safe_redirect( $redirect_url );
3878 exit();
3879 }
3880
3881 function wpforo_settings_get_slug( $key ): string {
3882 $slug = wpfval( WPF()->settings->slugs, $key );
3883 if( ! $slug ) $slug = $key;
3884
3885 return strtolower( (string) $slug );
3886 }
3887
3888 function wpforo_settings_get_slug_key( $slug ) {
3889 $ret = array_search( $slug, WPF()->settings->slugs );
3890 if( $ret === false ) $ret = $slug;
3891
3892 return $ret;
3893 }
3894
3895 function wpforo_is_slug_base( $slug ): bool {
3896 return ! wpfkey( WPF()->tpl->templates, wpforo_settings_get_slug_key( $slug ) );
3897 }
3898
3899 /**
3900 * @param string $group
3901 * @param string|int ...$name ... $_, ... [optional] more keys
3902 *
3903 * @return mixed|null
3904 */
3905 function wpforo_setting( $group, $name ) {
3906 if( ! is_null( WPF()->settings ) && property_exists( WPF()->settings, $group ) ) {
3907 $args = func_get_args();
3908 $args[0] = &WPF()->settings->$group;
3909
3910 return call_user_func_array( 'wpfval', $args );
3911 }
3912
3913 return null;
3914 }
3915
3916 function _wpforo_apply_email_shortcodes( $txt, $args ) {
3917 $forumid = (int) wpfval( $args, 'forumid' );
3918 $topicid = wpforo_bigintval( wpfval( $args, 'topicid' ) );
3919 $postid = wpforo_bigintval( wpfval( $args, 'postid' ) );
3920 $user = (array) wpfval( $args, 'user' );
3921 $owner = (array) wpfval( $args, 'owner' );
3922 $unsubscribe_link = (string) wpfval( $args, 'unsubscribe_link' );
3923
3924 $txt = do_shortcode( $txt );
3925
3926 return preg_replace_callback( '#\[[^\[\]]+?]#iu', function( $match ) use ( $forumid, $topicid, $postid, $user, $owner, $unsubscribe_link ) {
3927 $body_length = apply_filters( 'wpforo_email_notification_post_body_length', 1000 );
3928 $value = '';
3929 $shortcode = $match[0];
3930
3931 if( $shortcode === '[unsubscribe_link]' ) {
3932 if( $unsubscribe_link ) {
3933 $value = sprintf( '<br><a target="_blank" href="%1$s">%2$s</a>', esc_url( (string) $unsubscribe_link ), wpforo_phrase( 'Unsubscribe', false ) );
3934 $value = stripslashes( $value );
3935 }
3936 } elseif( strpos( (string) $shortcode, '[user_' ) === 0 ) {
3937 if( wpforo_bigintval( wpfval( $user, 'userid' ) ) ) {
3938 $value = wpforo_user_field_shortcode_to_value( $shortcode, $user['userid'] );
3939 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3940 $value = '';
3941 }
3942 } else {
3943 if( $shortcode === '[user_display_name]' ) {
3944 $value = wpfval( $user, 'display_name' ) ?: wpforo_phrase( 'Anonymous', false );
3945 } elseif( $shortcode === '[user_user_email]' ) {
3946 $value = (string) wpfval( $user, 'user_email' );
3947 }
3948 }
3949 } elseif( strpos( (string) $shortcode, '[owner_' ) === 0 ) {
3950 if( wpforo_bigintval( wpfval( $owner, 'userid' ) ) ) {
3951 $value = wpforo_user_field_shortcode_to_value( $shortcode, $owner['userid'] );
3952 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3953 $value = '';
3954 }
3955 } else {
3956 if( $shortcode === '[owner_display_name]' ) {
3957 $value = wpfval( $owner, 'display_name' ) ?: wpforo_phrase( 'Anonymous', false );
3958 } elseif( $shortcode === '[owner_user_email]' ) {
3959 $value = (string) wpfval( $owner, 'user_email' );
3960 }
3961 }
3962 } elseif( strpos( (string) $shortcode, '[forum_' ) === 0 ) {
3963 if( $forumid ) {
3964 $value = wpforo_forum_field_shortcode_to_value( $shortcode, $forumid );
3965 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3966 $value = '';
3967 }
3968 }
3969 } elseif( strpos( (string) $shortcode, '[topic_' ) === 0 ) {
3970 if( $topicid ) {
3971 $value = wpforo_topic_field_shortcode_to_value( $shortcode, $topicid );
3972 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3973 $value = '';
3974 } elseif( $shortcode === '[topic_body]' ) {
3975 $value = wpforo_text( wpforo_kses( wpforo_content_filter( $value ), 'email' ), $body_length, false, false, false, true, false );
3976 }
3977 }
3978 } elseif( strpos( (string) $shortcode, '[post_' ) === 0 ) {
3979 if( $postid ) {
3980 $value = wpforo_post_field_shortcode_to_value( $shortcode, $postid );
3981 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3982 $value = '';
3983 } elseif( $shortcode === '[post_body]' ) {
3984 $value = wpforo_text( wpforo_kses( wpforo_content_filter( $value ), 'email' ), $body_length, false, false, false, true, false );
3985 }
3986 }
3987 }
3988
3989 return $value;
3990 }, (string) $txt );
3991 }
3992
3993 function wpforo_apply_email_shortcodes( $txt, $pitem, $item, $owner, $user, $unsubscribe_link ) {
3994 $forumid = ( intval( wpfval( $pitem, 'forumid' ) ) ?: intval( wpfval( $item, 'forumid' ) ) );
3995 $topicid = ( wpforo_bigintval( wpfval( $pitem, 'topicid' ) ) ?: wpforo_bigintval( wpfval( $item, 'topicid' ) ) );
3996 $postid = ( wpforo_bigintval( wpfval( $item, 'postid' ) ) ?: wpforo_bigintval( wpfval( $item, 'first_postid' ) ) );
3997
3998 return _wpforo_apply_email_shortcodes( $txt, compact( 'forumid', 'topicid', 'postid', 'owner', 'user', 'unsubscribe_link' ) );
3999 }
4000
4001 function wpforo_send_email( $email, $sbj, $msg, $headers = '', $context = 'general', $related_id = 0 ) {
4002 if( defined( 'IS_GO2WPFORO' ) && IS_GO2WPFORO ) return false;
4003 if( apply_filters( 'break_wpforo_send_email', false, $email, $sbj, $msg, $headers ) ) return false;
4004 $key = [ $email, $sbj, substr( $msg, 0, 100 ) ];
4005 if( WPF()->ram_cache->exists( $key ) ) return false;
4006
4007 if( isset( WPF()->email_queue ) ) {
4008 $result = WPF()->email_queue->queue_or_send( $email, $sbj, $msg, $headers, $context, $related_id );
4009 } else {
4010 add_filter( 'wp_mail_content_type', 'wpforo_set_html_content_type', 999 );
4011 $result = wp_mail( $email, $sbj, $msg, ( $headers ?: wpforo_mail_headers() ) );
4012 remove_filter( 'wp_mail_content_type', 'wpforo_set_html_content_type' );
4013 }
4014
4015 if( $result ) {
4016 WPF()->ram_cache->set( $key, true );
4017 return true;
4018 }
4019
4020 return false;
4021 }
4022
4023 function wpforo_get_site_default_locale() {
4024 $locale = trim( (string) get_option( 'WPLANG', 'en_US' ) );
4025 if( ! $locale ) $locale = 'en_US';
4026
4027 return $locale;
4028 }
4029
4030 function wpforo_sanitize( $action, $variable_name, $filter, $default = '' ) {
4031 if( $filter === "FILTER_SANITIZE_STRING" ) {
4032 $glob = INPUT_POST === $action ? $_POST : $_GET;
4033 if( wpfkey( $glob, $variable_name ) ) {
4034 return sanitize_text_field( $glob[ $variable_name ] );
4035 } else {
4036 return $default;
4037 }
4038 }
4039 $variable = isset( $variable_name ) ? filter_input( $action, $variable_name, $filter ) : '';
4040
4041 return $variable ?: $default;
4042 }
4043
4044 function wpforo_length( $str, $filtered = true ) {
4045 $str = $filtered ? strip_tags( trim( (string) $str ) ) : $str;
4046 if( function_exists( 'mb_substr' ) ) {
4047 $length = mb_strlen( (string) $str, get_option( 'blog_charset' ) );
4048 } else {
4049 $length = strlen( (string) $str );
4050 }
4051
4052 return (int) $length;
4053 }
4054
4055 if( ! function_exists( 'wpforo_remote_retrieve_header' ) ) {
4056 /**
4057 *
4058 * @param array|WP_Error $response HTTP response.
4059 * @param string $header Header name to retrieve value from.
4060 *
4061 * @return string string The header value. Empty string on if incorrect parameter given, or if the header doesn't exist.
4062 */
4063 function wpforo_remote_retrieve_header( $response, $header ) {
4064 $header = wp_remote_retrieve_header( $response, $header );
4065 if( is_array( $header ) ) $header = (string) array_pop( $header );
4066
4067 return $header;
4068 }
4069 }
4070
4071 function wpforo_is_array_of_scalars( $array ) {
4072 if( ! is_array( $array ) ) return false;
4073 foreach( $array as $element ) if( ! is_scalar( $element ) ) return false;
4074
4075 return true;
4076 }
4077
4078 function wpforo_is_gzipped( $contents ): bool {
4079 // phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found
4080 if( function_exists( 'mb_strpos' ) ) {
4081 return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
4082 } else {
4083 return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
4084 }
4085 // phpcs:enable
4086 }
4087
4088 function wpforo_sanitize_svg( $file ): bool {
4089 $dirty = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
4090
4091 // Is the SVG gzipped? If so we try and decode the string
4092 $is_zipped = wpforo_is_gzipped( $dirty );
4093 if( $is_zipped ) {
4094 $dirty = gzdecode( $dirty );
4095
4096 // If decoding fails, bail as we're not secure
4097 if( false === $dirty ) {
4098 return false;
4099 }
4100 }
4101
4102 $clean = trim( wpforo_kses( $dirty, 'svg' ) );
4103
4104 if( ! $clean ) {
4105 return false;
4106 }
4107
4108 // If we were gzipped, we need to re-zip
4109 if( $is_zipped ) {
4110 $clean = gzencode( $clean );
4111 }
4112
4113 file_put_contents( $file, $clean ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
4114
4115 return true;
4116 }
4117
4118 function wpforo_check_for_svg( $file ) {
4119 // Ensure we have a proper file path before processing
4120 if( ! isset( $file['tmp_name'] ) ) {
4121 return $file;
4122 }
4123
4124 $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], ( $file['name'] ?? '' ), [] );
4125 $type = ! empty( $wp_filetype['type'] ) ? $wp_filetype['type'] : (string) wpfval( $file, 'type' );
4126
4127 if( in_array( $type, [ 'image/svg+xml', 'image/svg' ], true ) ) {
4128 if( ! wpforo_sanitize_svg( $file['tmp_name'] ) ) {
4129 $file['error'] = __(
4130 "Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded",
4131 'wpforo'
4132 );
4133 }
4134 }
4135
4136 return $file;
4137 }
4138
4139
4140 /**
4141 * Custom function to replace is_numeric() function avoiding negative
4142 * numbers to be considered as numeric. This is mostly used differentiating
4143 * between ID and slugs. Sometimes slugs can be like "-12345".
4144 *
4145 * @param mixed $value
4146 *
4147 * @return bool
4148 */
4149 function wpforo_is_id( $value ) {
4150 return is_numeric( $value ) && (int) $value >= 0;
4151 }
4152
4153 function is_avatar_url( string $url ): bool {
4154 // Check if the string is a valid URL
4155 if( filter_var( $url, FILTER_VALIDATE_URL ) ) {
4156 // Make a HEAD request to check the Content-Type
4157 $response = wp_safe_remote_head( $url );
4158
4159 if( ! is_wp_error( $response ) ) {
4160 $content_type = wp_remote_retrieve_header( $response, 'content-type' );
4161
4162 // Check if the Content-Type is an image
4163 if( strpos( $content_type, 'image/' ) !== false ) {
4164 return true; // The URL points to an image
4165 }
4166 }
4167 }
4168
4169 return false; // Not a valid avatar URL
4170 }
4171
4172 /**
4173 * Function to generate image object for DiscussionForumPosting schema JSON-LD
4174 *
4175 * @param string $content
4176 *
4177 * @return string
4178 */
4179 function wpforo_generate_scheme_image_object( $content ) {
4180 $image = '';
4181 if( function_exists( 'WPF_ATTACH' ) ) {
4182 $content = WPF_ATTACH()->tools->do_shortcodes( $content );
4183 }
4184 # TODO: Add support for secure URLs w/o image extensions
4185 # Currently, only URLs with image extensions are supported
4186 # by wpforo_find_image_urls() function.
4187 $images = wpforo_find_image_urls( $content, false );
4188 if( ! empty( $images ) ) {
4189 $image = '"image": [';
4190 foreach( $images as $img ) {
4191 $image .= '{ "@type": "ImageObject", "url": "' . esc_url_raw( $img ) . '"},';
4192 }
4193 $image = trim( $image, ',' ) . '],';
4194 }
4195
4196 return $image;
4197 }
4198
4199 function wpforo_generate_scheme_author_object( $user_id ) {
4200 $author = '';
4201 $post_member = wpforo_member( $user_id );
4202 $author_posts = wpfval( $post_member, 'posts' ) ? $post_member['posts'] : 1;
4203 $author_name = wpfval( $post_member, 'display_name' ) ? $post_member['display_name'] : 'Guest';
4204 $author_url = wpfval( $post_member, 'profile_url' ) ? '
4205 "url": "' . esc_url_raw( $post_member['profile_url'] ) . '",' : '';
4206 $author = ',
4207 "author": {
4208 "@type": "Person",
4209 "name": "' . esc_attr( $author_name ) . '",' . $author_url . '
4210 "agentInteractionStatistic": {
4211 "@type": "InteractionCounter",
4212 "interactionType": "https://schema.org/WriteAction",
4213 "userInteractionCount": ' . intval( $author_posts ) . '
4214 }
4215 }';
4216
4217 return $author;
4218 }
4219
4220 /**
4221 * Get wpForo home URL for a specific language/locale
4222 *
4223 * @param string $language Language code (e.g., 'de_DE', 'en_US', 'fr_FR')
4224 * @return string Forum home URL for the specified language
4225 */
4226 function wpforo_get_home_url_for_language( $language ) {
4227 $sql = WPF()->db->prepare(
4228 "SELECT * FROM " . WPF()->tables->boards . " WHERE status = 1 AND locale = %s LIMIT 1",
4229 sanitize_locale_name($language)
4230 );
4231 $board = (array) WPF()->db->get_row( $sql, ARRAY_A );
4232 if( $board ) $board = WPF()->board->decode( $board );
4233
4234 if( !$board ) {
4235 return home_url(); // Fallback to default
4236 }
4237 return trailingslashit( home_url( $board['slug'] ) );
4238 }
4239
4240 function wpforo_get_current_language_url( $url = '' ) {
4241 $url = ( $url ) ? $url : '';
4242 if( function_exists( 'pll_current_language' ) && $language = pll_current_language( 'locale' ) ) {
4243 $sql = WPF()->db->prepare(
4244 "SELECT * FROM " . WPF()->tables->boards . " WHERE status = 1 AND locale = %s LIMIT 1",
4245 sanitize_locale_name($language)
4246 );
4247 $board = (array) WPF()->db->get_row( $sql, ARRAY_A );
4248 if( $boardid = wpfval($board, 'boardid') ){
4249 $url = ( $url ) ? $url : $_SERVER['REQUEST_URI'];
4250 $url = add_query_arg( 'boardid', intval( $boardid ), $_SERVER['REQUEST_URI'] );
4251 }
4252 }
4253 return $url;
4254 }
4255
4256 /**
4257 * Centralized logging function for wpForo AI features.
4258 *
4259 * Nothing is logged unless wpforo General > Debug Mode is enabled
4260 * OR WordPress WP_DEBUG is on. This ensures zero disk usage on production sites.
4261 *
4262 * Log levels: 'error', 'info', 'debug'
4263 * - error: Logged when WP_DEBUG is on OR wpforo Debug Mode is on
4264 * - info: Logged only when BOTH WP_DEBUG and wpforo Debug Mode are on
4265 * - debug: Logged when wpforo Debug Mode is on (with or without WP_DEBUG)
4266 *
4267 * Logs are written to: wp-content/uploads/wpforo/ai-logs/{Y-m-d}.log
4268 *
4269 * @param string $level Log level: 'error', 'info', or 'debug'
4270 * @param string $message Log message
4271 * @param string $tag Component tag, e.g. 'Moderation', 'Client', 'VectorStorage'
4272 */
4273 function wpforo_ai_log( $level, $message, $tag = '' ) {
4274 static $enabled_levels = null;
4275 static $log_dir = null;
4276
4277 // Resolve enabled levels once per request
4278 if ( $enabled_levels === null ) {
4279 $enabled_levels = [];
4280 $wpforo_debug = wpforo_setting( 'general', 'debug_mode' );
4281 $wp_debug = defined( 'WP_DEBUG' ) && WP_DEBUG;
4282 if ( $wp_debug || $wpforo_debug ) {
4283 $enabled_levels[] = 'error';
4284 }
4285 if ( $wpforo_debug ) {
4286 $enabled_levels[] = 'debug';
4287 }
4288 if ( $wp_debug && $wpforo_debug ) {
4289 $enabled_levels[] = 'info';
4290 }
4291 }
4292
4293 if ( ! in_array( $level, $enabled_levels, true ) ) {
4294 return;
4295 }
4296
4297 // Resolve log directory once per request
4298 if ( $log_dir === null ) {
4299 $upload_dir = wp_upload_dir();
4300 $log_dir = $upload_dir['basedir'] . '/wpforo/ai-logs';
4301 if ( ! is_dir( $log_dir ) ) {
4302 wp_mkdir_p( $log_dir );
4303 @file_put_contents( $log_dir . '/index.php', "<?php // Silence is golden.\n" );
4304 }
4305 }
4306
4307 $prefix = '[wpForo AI' . ( $tag ? ' ' . $tag : '' ) . ']';
4308 $date = gmdate( 'Y-m-d H:i:s' );
4309 $entry = sprintf( "[%s] [%s] %s %s\n", $date, strtoupper( $level ), $prefix, $message );
4310
4311 $log_file = $log_dir . '/' . gmdate( 'Y-m-d' ) . '.log';
4312 @file_put_contents( $log_file, $entry, FILE_APPEND | LOCK_EX );
4313 }
4314
4315 /**
4316 * Get the master list of AI-supported languages.
4317 *
4318 * Single source of truth for all AI language lists across the plugin:
4319 * - Admin settings dropdowns (Settings.php)
4320 * - Frontend widget preference dropdown (header.php)
4321 * - Language name resolution (AIClient.php get_user_language)
4322 * - Preference validation whitelist (AIClient.php ajax_save_ai_preferences)
4323 *
4324 * Each entry: [ 'code' => '2-letter ISO code', 'locale' => 'WordPress locale', 'name' => 'English name', 'native' => 'Native name' ]
4325 *
4326 * @return array
4327 */
4328 function wpforo_get_ai_languages() {
4329 return [
4330 // Major world languages
4331 [ 'code' => 'en', 'locale' => 'en_US', 'name' => 'English', 'native' => 'English' ],
4332 [ 'code' => 'en', 'locale' => 'en_GB', 'name' => 'English (UK)', 'native' => 'English (UK)' ],
4333 [ 'code' => 'zh', 'locale' => 'zh_CN', 'name' => 'Chinese Simplified', 'native' => '简体中文' ],
4334 [ 'code' => 'zh', 'locale' => 'zh_TW', 'name' => 'Chinese Traditional', 'native' => '繁體中文' ],
4335 [ 'code' => 'es', 'locale' => 'es_ES', 'name' => 'Spanish', 'native' => 'Español' ],
4336 [ 'code' => 'hi', 'locale' => 'hi_IN', 'name' => 'Hindi', 'native' => 'हिन्दी' ],
4337 [ 'code' => 'ar', 'locale' => 'ar', 'name' => 'Arabic', 'native' => 'العربية' ],
4338 [ 'code' => 'pt', 'locale' => 'pt_BR', 'name' => 'Portuguese (Brazil)', 'native' => 'Português Brasil' ],
4339 [ 'code' => 'pt', 'locale' => 'pt_PT', 'name' => 'Portuguese', 'native' => 'Português' ],
4340 [ 'code' => 'bn', 'locale' => 'bn_BD', 'name' => 'Bengali', 'native' => 'বাংলা' ],
4341 [ 'code' => 'ru', 'locale' => 'ru_RU', 'name' => 'Russian', 'native' => 'Русский' ],
4342 [ 'code' => 'ja', 'locale' => 'ja', 'name' => 'Japanese', 'native' => '日本語' ],
4343 [ 'code' => 'fr', 'locale' => 'fr_FR', 'name' => 'French', 'native' => 'Français' ],
4344 [ 'code' => 'de', 'locale' => 'de_DE', 'name' => 'German', 'native' => 'Deutsch' ],
4345 [ 'code' => 'ko', 'locale' => 'ko_KR', 'name' => 'Korean', 'native' => '한국어' ],
4346 [ 'code' => 'it', 'locale' => 'it_IT', 'name' => 'Italian', 'native' => 'Italiano' ],
4347 [ 'code' => 'tr', 'locale' => 'tr_TR', 'name' => 'Turkish', 'native' => 'Türkçe' ],
4348 [ 'code' => 'vi', 'locale' => 'vi_VN', 'name' => 'Vietnamese', 'native' => 'Tiếng Việt' ],
4349 [ 'code' => 'pl', 'locale' => 'pl_PL', 'name' => 'Polish', 'native' => 'Polski' ],
4350 [ 'code' => 'nl', 'locale' => 'nl_NL', 'name' => 'Dutch', 'native' => 'Nederlands' ],
4351 [ 'code' => 'th', 'locale' => 'th', 'name' => 'Thai', 'native' => 'ไทย' ],
4352 [ 'code' => 'id', 'locale' => 'id_ID', 'name' => 'Indonesian', 'native' => 'Bahasa Indonesia' ],
4353 [ 'code' => 'ms', 'locale' => 'ms_MY', 'name' => 'Malay', 'native' => 'Bahasa Melayu' ],
4354 // European languages
4355 [ 'code' => 'uk', 'locale' => 'uk', 'name' => 'Ukrainian', 'native' => 'Українська' ],
4356 [ 'code' => 'ro', 'locale' => 'ro_RO', 'name' => 'Romanian', 'native' => 'Română' ],
4357 [ 'code' => 'el', 'locale' => 'el_GR', 'name' => 'Greek', 'native' => 'Ελληνικά' ],
4358 [ 'code' => 'cs', 'locale' => 'cs_CZ', 'name' => 'Czech', 'native' => 'Čeština' ],
4359 [ 'code' => 'sv', 'locale' => 'sv_SE', 'name' => 'Swedish', 'native' => 'Svenska' ],
4360 [ 'code' => 'hu', 'locale' => 'hu_HU', 'name' => 'Hungarian', 'native' => 'Magyar' ],
4361 [ 'code' => 'da', 'locale' => 'da_DK', 'name' => 'Danish', 'native' => 'Dansk' ],
4362 [ 'code' => 'fi', 'locale' => 'fi', 'name' => 'Finnish', 'native' => 'Suomi' ],
4363 [ 'code' => 'no', 'locale' => 'no_NO', 'name' => 'Norwegian', 'native' => 'Norsk' ],
4364 [ 'code' => 'sk', 'locale' => 'sk_SK', 'name' => 'Slovak', 'native' => 'Slovenčina' ],
4365 [ 'code' => 'bg', 'locale' => 'bg_BG', 'name' => 'Bulgarian', 'native' => 'Български' ],
4366 [ 'code' => 'hr', 'locale' => 'hr', 'name' => 'Croatian', 'native' => 'Hrvatski' ],
4367 [ 'code' => 'sr', 'locale' => 'sr_RS', 'name' => 'Serbian', 'native' => 'Српски' ],
4368 [ 'code' => 'sl', 'locale' => 'sl_SI', 'name' => 'Slovenian', 'native' => 'Slovenščina' ],
4369 [ 'code' => 'lt', 'locale' => 'lt_LT', 'name' => 'Lithuanian', 'native' => 'Lietuvių' ],
4370 [ 'code' => 'lv', 'locale' => 'lv', 'name' => 'Latvian', 'native' => 'Latviešu' ],
4371 [ 'code' => 'et', 'locale' => 'et', 'name' => 'Estonian', 'native' => 'Eesti' ],
4372 [ 'code' => 'ca', 'locale' => 'ca', 'name' => 'Catalan', 'native' => 'Català' ],
4373 [ 'code' => 'gl', 'locale' => 'gl_ES', 'name' => 'Galician', 'native' => 'Galego' ],
4374 [ 'code' => 'eu', 'locale' => 'eu', 'name' => 'Basque', 'native' => 'Euskara' ],
4375 [ 'code' => 'sq', 'locale' => 'sq', 'name' => 'Albanian', 'native' => 'Shqip' ],
4376 [ 'code' => 'mk', 'locale' => 'mk_MK', 'name' => 'Macedonian', 'native' => 'Македонски' ],
4377 [ 'code' => 'bs', 'locale' => 'bs_BA', 'name' => 'Bosnian', 'native' => 'Bosanski' ],
4378 [ 'code' => 'mt', 'locale' => 'mt_MT', 'name' => 'Maltese', 'native' => 'Malti' ],
4379 [ 'code' => 'is', 'locale' => 'is_IS', 'name' => 'Icelandic', 'native' => 'Íslenska' ],
4380 [ 'code' => 'ga', 'locale' => 'ga', 'name' => 'Irish', 'native' => 'Gaeilge' ],
4381 [ 'code' => 'cy', 'locale' => 'cy', 'name' => 'Welsh', 'native' => 'Cymraeg' ],
4382 [ 'code' => 'be', 'locale' => 'be_BY', 'name' => 'Belarusian', 'native' => 'Беларуская' ],
4383 [ 'code' => 'lb', 'locale' => 'lb_LU', 'name' => 'Luxembourgish', 'native' => 'Lëtzebuergesch' ],
4384 // Middle Eastern & Central Asian
4385 [ 'code' => 'he', 'locale' => 'he_IL', 'name' => 'Hebrew', 'native' => 'עברית' ],
4386 [ 'code' => 'fa', 'locale' => 'fa_IR', 'name' => 'Persian', 'native' => 'فارسی' ],
4387 [ 'code' => 'ur', 'locale' => 'ur', 'name' => 'Urdu', 'native' => 'اردو' ],
4388 [ 'code' => 'ku', 'locale' => 'ku', 'name' => 'Kurdish', 'native' => 'Kurdî' ],
4389 [ 'code' => 'az', 'locale' => 'az', 'name' => 'Azerbaijani', 'native' => 'Azərbaycan' ],
4390 [ 'code' => 'ka', 'locale' => 'ka_GE', 'name' => 'Georgian', 'native' => 'ქართული' ],
4391 [ 'code' => 'hy', 'locale' => 'hy', 'name' => 'Armenian', 'native' => 'Հայերեն' ],
4392 [ 'code' => 'kk', 'locale' => 'kk', 'name' => 'Kazakh', 'native' => 'Қазақша' ],
4393 [ 'code' => 'uz', 'locale' => 'uz_UZ', 'name' => 'Uzbek', 'native' => 'Oʻzbek' ],
4394 [ 'code' => 'tg', 'locale' => 'tg', 'name' => 'Tajik', 'native' => 'Тоҷикӣ' ],
4395 [ 'code' => 'ky', 'locale' => 'ky_KY', 'name' => 'Kyrgyz', 'native' => 'Кыргызча' ],
4396 [ 'code' => 'tk', 'locale' => 'tk', 'name' => 'Turkmen', 'native' => 'Türkmen' ],
4397 [ 'code' => 'mn', 'locale' => 'mn', 'name' => 'Mongolian', 'native' => 'Монгол' ],
4398 // South Asian
4399 [ 'code' => 'ta', 'locale' => 'ta_IN', 'name' => 'Tamil', 'native' => 'தமிழ்' ],
4400 [ 'code' => 'te', 'locale' => 'te', 'name' => 'Telugu', 'native' => 'తెలుగు' ],
4401 [ 'code' => 'mr', 'locale' => 'mr', 'name' => 'Marathi', 'native' => 'मराठी' ],
4402 [ 'code' => 'gu', 'locale' => 'gu', 'name' => 'Gujarati', 'native' => 'ગુજરાતી' ],
4403 [ 'code' => 'kn', 'locale' => 'kn', 'name' => 'Kannada', 'native' => 'ಕನ್ನಡ' ],
4404 [ 'code' => 'ml', 'locale' => 'ml_IN', 'name' => 'Malayalam', 'native' => 'മലയാളം' ],
4405 [ 'code' => 'pa', 'locale' => 'pa_IN', 'name' => 'Punjabi', 'native' => 'ਪੰਜਾਬੀ' ],
4406 [ 'code' => 'si', 'locale' => 'si_LK', 'name' => 'Sinhala', 'native' => 'සිංහල' ],
4407 [ 'code' => 'ne', 'locale' => 'ne_NP', 'name' => 'Nepali', 'native' => 'नेपाली' ],
4408 [ 'code' => 'my', 'locale' => 'my_MM', 'name' => 'Burmese', 'native' => 'မြန်မာ' ],
4409 [ 'code' => 'km', 'locale' => 'km', 'name' => 'Khmer', 'native' => 'ខ្មែរ' ],
4410 [ 'code' => 'lo', 'locale' => 'lo', 'name' => 'Lao', 'native' => 'ລາວ' ],
4411 // East & Southeast Asian
4412 [ 'code' => 'tl', 'locale' => 'tl', 'name' => 'Filipino', 'native' => 'Filipino' ],
4413 [ 'code' => 'jv', 'locale' => 'jv_ID', 'name' => 'Javanese', 'native' => 'Basa Jawa' ],
4414 [ 'code' => 'su', 'locale' => 'su_ID', 'name' => 'Sundanese', 'native' => 'Basa Sunda' ],
4415 // African languages
4416 [ 'code' => 'sw', 'locale' => 'sw', 'name' => 'Swahili', 'native' => 'Kiswahili' ],
4417 [ 'code' => 'am', 'locale' => 'am', 'name' => 'Amharic', 'native' => 'አማርኛ' ],
4418 [ 'code' => 'ha', 'locale' => 'ha', 'name' => 'Hausa', 'native' => 'Hausa' ],
4419 [ 'code' => 'yo', 'locale' => 'yo', 'name' => 'Yoruba', 'native' => 'Yorùbá' ],
4420 [ 'code' => 'ig', 'locale' => 'ig_NG', 'name' => 'Igbo', 'native' => 'Igbo' ],
4421 [ 'code' => 'zu', 'locale' => 'zu', 'name' => 'Zulu', 'native' => 'isiZulu' ],
4422 [ 'code' => 'xh', 'locale' => 'xh', 'name' => 'Xhosa', 'native' => 'isiXhosa' ],
4423 [ 'code' => 'af', 'locale' => 'af', 'name' => 'Afrikaans', 'native' => 'Afrikaans' ],
4424 [ 'code' => 'so', 'locale' => 'so_SO', 'name' => 'Somali', 'native' => 'Soomaali' ],
4425 [ 'code' => 'rw', 'locale' => 'rw_RW', 'name' => 'Kinyarwanda', 'native' => 'Kinyarwanda' ],
4426 [ 'code' => 'mg', 'locale' => 'mg_MG', 'name' => 'Malagasy', 'native' => 'Malagasy' ],
4427 // Other
4428 [ 'code' => 'eo', 'locale' => 'eo', 'name' => 'Esperanto', 'native' => 'Esperanto' ],
4429 [ 'code' => 'la', 'locale' => 'la', 'name' => 'Latin', 'native' => 'Latina' ],
4430 ];
4431 }
4432