PluginProbe
wpForo Forum / 3.1.6
wpForo Forum v3.1.6
3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 138 releases
wpforo / includes / functions.php

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

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