PluginProbe
wpForo Forum / 3.0.7
wpForo Forum v3.0.7
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 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / includes / functions.php

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

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