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

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

4,324 lines 178.3 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 function wpforo_extra_html_parser( $extra_html = '', $allowed_html = [] ) {
2831 if( $extra_html ) {
2832 $extra_html = explode( ',', $extra_html );
2833 $extra_html = array_filter( $extra_html );
2834 if( ! empty( $extra_html ) ) {
2835 foreach( $extra_html as $html ) {
2836 $html = trim( (string) $html );
2837 if( preg_match( '|([^()]+)\((.+)\)|', $html, $item ) ) {
2838 if( wpfval( $item, 1 ) && wpfval( $item, 2 ) ) {
2839 $attrs = explode( ' ', $item[2] );
2840 $attrs = array_map( 'trim', $attrs );
2841 foreach( $attrs as $attr ) {
2842 $allowed_html[ $item[1] ][ $attr ] = [];
2843 }
2844 }
2845 } else {
2846 $allowed_html[ $html ] = [];
2847 }
2848 }
2849 }
2850 }
2851
2852 return $allowed_html;
2853 }
2854
2855 function wpforo_clear_array( $array, $clear = [], $by = 'value' ) {
2856 if( is_array( $clear ) && ! empty( $clear ) ) {
2857 foreach( $clear as $ext ) {
2858 if( $by == 'value' ) {
2859 if( ( $key = array_search( $ext, $array ) ) !== false ) {
2860 unset( $array[ $key ] );
2861 }
2862 } elseif( $by == 'key' ) {
2863 if( wpfkey( $array, $ext ) ) unset( $array[ $ext ] );
2864 }
2865 }
2866 } elseif( is_string( $clear ) || is_numeric( $clear ) ) {
2867 if( wpfval( $array, $clear ) ) unset( $array[ $clear ] );
2868 }
2869
2870 return $array;
2871 }
2872
2873 function wpforo_key( $array = [], $value = '', $type = 'default' ) {
2874 $keys = [];
2875 if( is_array( $array ) && ! empty( $array ) ) {
2876 foreach( $array as $k => $v ) {
2877 if( $v === $value ) {
2878 $keys[] = $k;
2879 }
2880 }
2881 }
2882 if( $type === 'sort' ) {
2883 sort( $keys );
2884
2885 return $keys;
2886 } else {
2887 return $keys;
2888 }
2889 }
2890
2891 function wpforo_unslashe( $data ) {
2892 $data = is_array( $data ) ? array_map( 'wpforo_unslashe', $data ) : stripslashes( (string) $data );
2893
2894 return $data;
2895 }
2896
2897 function wpforo_encode( $data ) {
2898 $data = is_array( $data ) ? array_map( 'wpforo_encode', $data ) : htmlspecialchars( $data, ENT_QUOTES );
2899
2900 return $data;
2901 }
2902
2903 function wpforo_decode( $data ) {
2904 $data = is_array( $data ) ? array_map( 'wpforo_decode', $data ) : htmlspecialchars_decode( $data, ENT_QUOTES );
2905
2906 return $data;
2907 }
2908
2909 function wpforo_trim( $data ) {
2910 $data = is_array( $data ) ? array_map( 'wpforo_trim', $data ) : trim( (string) $data );
2911
2912 return $data;
2913 }
2914
2915 function wpforo_sanitize_int( $data ) {
2916 $data = is_array( $data ) ? array_map( 'wpforo_sanitize_int', $data ) : intval( $data );
2917
2918 return $data;
2919 }
2920
2921 function wpforo_sanitize_text( $data ) {
2922 $data = is_array( $data ) ? array_map( 'wpforo_sanitize_text', $data ) : sanitize_text_field( $data );
2923
2924 return $data;
2925 }
2926
2927 /**
2928 * Sanitize orderby parameter for SQL queries.
2929 * Validates against a whitelist of allowed column names to prevent SQL injection.
2930 *
2931 * @param string $orderby The orderby value to sanitize
2932 * @param string $context The context: 'topics', 'posts', 'members', or 'search'
2933 * @param string $default Default value if validation fails
2934 * @return string Sanitized orderby value
2935 */
2936 function wpforo_sanitize_orderby( $orderby, $context = 'topics', $default = '' ) {
2937 $orderby = sanitize_text_field( $orderby );
2938
2939 // Define allowed columns for each context
2940 $allowed = [
2941 'topics' => [
2942 'topicid', 'forumid', 'userid', 'title', 'slug', 'created', 'modified',
2943 'views', 'posts', 'type', 'status', 'private', 'closed', 'solved',
2944 'has_attach', 'first_postid', 'last_postid', 'pollid', 'prefix'
2945 ],
2946 'posts' => [
2947 'postid', 'forumid', 'topicid', 'userid', 'title', 'created', 'modified',
2948 'status', 'private', 'is_answer', 'is_first_post', 'votes', 'root', 'parentid'
2949 ],
2950 'members' => [
2951 'userid', 'posts', 'questions', 'answers', 'comments', 'reactions', 'points',
2952 'online_time', 'registered', 'display_name', 'user_registered'
2953 ],
2954 'search' => [
2955 'relevancy', 'date', 'user', 'forum', 'created', 'modified'
2956 ],
2957 ];
2958
2959 // Get the whitelist for this context
2960 $whitelist = isset( $allowed[$context] ) ? $allowed[$context] : [];
2961
2962 // Also allow common aliases
2963 $whitelist = array_merge( $whitelist, [ 'id', 'date', 'name' ] );
2964
2965 // Check if orderby is in the whitelist (case-insensitive)
2966 $orderby_lower = strtolower( $orderby );
2967 foreach( $whitelist as $allowed_col ) {
2968 if( strtolower( $allowed_col ) === $orderby_lower ) {
2969 return $allowed_col; // Return the properly cased version
2970 }
2971 }
2972
2973 // If not in whitelist, return the default
2974 return $default;
2975 }
2976
2977 if( ! function_exists( 'sanitize_textarea_field' ) && ! function_exists( '_sanitize_text_fields' ) ) {
2978 function sanitize_textarea_field( $str ) {
2979 $filtered = _sanitize_text_fields( $str, true );
2980
2981 return apply_filters( 'sanitize_textarea_field', $filtered, $str );
2982 }
2983
2984 function _sanitize_text_fields( $str, $keep_newlines = false ) {
2985 $filtered = wp_check_invalid_utf8( $str );
2986 if( strpos( (string) $filtered, '<' ) !== false ) {
2987 $filtered = wp_pre_kses_less_than( $filtered );
2988 $filtered = wp_strip_all_tags( $filtered, false );
2989 $filtered = str_replace( "<\n", "&lt;\n", $filtered );
2990 }
2991 if( ! $keep_newlines ) {
2992 $filtered = preg_replace( '/[\r\n\t ]+/', ' ', (string) $filtered );
2993 }
2994 $filtered = trim( (string) $filtered );
2995 $found = false;
2996 while( preg_match( '/%[a-f0-9]{2}/i', (string) $filtered, $match ) ) {
2997 $filtered = str_replace( $match[0], '', $filtered );
2998 $found = true;
2999 }
3000 if( $found ) {
3001 $filtered = trim( preg_replace( '/ +/', ' ', (string) $filtered ) );
3002 }
3003
3004 return $filtered;
3005 }
3006 }
3007
3008 /**
3009 * @param string $role
3010 *
3011 * @return bool
3012 */
3013 function wpforo_current_user_is( $role ) {
3014 $role = strtolower( (string) $role );
3015
3016 $filter_result = apply_filters( 'wpforo_current_user_is', null, $role );
3017 if( ! is_null( $filter_result ) ) return (bool) $filter_result;
3018
3019 switch( $role ) {
3020 case 'admin':
3021 if( current_user_can( 'activate_plugins' ) ) {
3022 return true;
3023 }
3024 if( current_user_can( 'install_plugins' ) ) {
3025 return true;
3026 }
3027 if( current_user_can( 'create_sites' ) ) {
3028 return true;
3029 }
3030 break;
3031 case 'moderator':
3032 if( WPF()->usergroup->can( 'aum' ) ) {
3033 return true;
3034 }
3035 if( current_user_can( 'moderate_comments' ) ) {
3036 return true;
3037 }
3038 if( current_user_can( 'edit_published_posts' ) ) {
3039 return true;
3040 }
3041 if( current_user_can( 'manage_categories' ) ) {
3042 return true;
3043 }
3044 break;
3045 }
3046
3047 return false;
3048 }
3049
3050 /**
3051 * @param int $userid
3052 * @param string $role
3053 *
3054 * @return bool
3055 */
3056 function wpforo_user_is( $userid, $role ) {
3057 $userid = wpforo_bigintval( $userid );
3058 $role = strtolower( (string) $role );
3059
3060 $filter_result = apply_filters( 'wpforo_user_is', null, $userid, $role );
3061 if( ! is_null( $filter_result ) ) return (bool) $filter_result;
3062
3063 switch( $role ) {
3064 case 'admin':
3065 if( user_can( $userid, 'activate_plugins' ) ) {
3066 return true;
3067 }
3068 if( user_can( $userid, 'install_plugins' ) ) {
3069 return true;
3070 }
3071 if( user_can( $userid, 'create_sites' ) ) {
3072 return true;
3073 }
3074 if( $user = WPF()->member->get_member( $userid ) ) {
3075 if( WPF()->usergroup->can( 'mf', $user['groupids'] ) || WPF()->usergroup->can( 'ms', $user['groupids'] ) ) {
3076 return true;
3077 }
3078 }
3079 break;
3080 case 'moderator':
3081 if( user_can( $userid, 'moderate_comments' ) ) {
3082 return true;
3083 }
3084 if( user_can( $userid, 'edit_published_posts' ) ) {
3085 return true;
3086 }
3087 if( user_can( $userid, 'manage_categories' ) ) {
3088 return true;
3089 }
3090 if( $user = WPF()->member->get_member( $userid ) ) {
3091 if( WPF()->usergroup->can( 'aum', $user['groupids'] ) ) {
3092 return true;
3093 }
3094 }
3095 break;
3096 }
3097
3098 return false;
3099 }
3100
3101 function wpforo_random_colors() {
3102 mt_srand( (int) ( microtime( true ) * 1000000 ) );
3103 $color = '';
3104 while( strlen( (string) $color ) < 6 ) {
3105 $color .= sprintf( "%02X", mt_rand( 0, 255 ) );
3106 }
3107
3108 return '#' . $color;
3109 }
3110
3111 /**
3112 * @param string $dir
3113 *
3114 * @return string
3115 */
3116 function wpforo_fix_dir_sep( string $dir ): string {
3117 return WPF()->fix_dir_sep( $dir );
3118 }
3119
3120 /**
3121 * @param string $url
3122 *
3123 * @return string
3124 */
3125 function wpforo_fix_url_sep( string $url ): string {
3126 return WPF()->fix_url_sep( $url );
3127 }
3128
3129 function wpforo_root_exist() {
3130 $args = [ 'table' => WPF()->tables->posts, 'col' => 'root', 'check' => 'col_exists' ];
3131
3132 return wpforo_db_check( $args );
3133 }
3134
3135 function wpforo_urlencode( $str ): string {
3136 $str = (string) $str;
3137 if( ! preg_match( '#^(\#post-\d+|https?:|s?ftp:)$#iu', (string) $str ) && ! preg_match( '#([?&][^?&/=\r\n]*=?[^?&/=\r\n]*)(?1)*$#iu', (string) $str ) && strpos(
3138 (string) $str,
3139 '~'
3140 ) === false && strpos(
3141 (string) $str,
3142 '*'
3143 ) === false && $str === urldecode(
3144 (string) $str
3145 ) ) {
3146 $str = urlencode( (string) $str );
3147 }
3148
3149 // Use wpforo_url_strtolower() instead of strtolower() to keep the exact values
3150 // of GET variables in pagination buttons. Otherwise, the GET values will differ
3151 // and the field value will be changed on the next page.
3152 // Example: on members search page the selected "field=Test" will be changed to "field=test"
3153 // on the next page. And the select field will loos current selected value.
3154 return $str !== urldecode( (string) $str ) ? wpforo_url_strtolower( (string) $str ) : $str;
3155 }
3156
3157 /**
3158 * Converts the URL to lowercase.
3159 *
3160 * This function converts the URL to lowercase. If the $include_query parameter is set to true,
3161 * the entire URL including the query string is converted to lowercase. Otherwise, only the part of the URL
3162 * before the query string is converted to lowercase.
3163 *
3164 * @param string $url The URL to be converted to lowercase.
3165 * @param bool $include_query Optional. Whether to include the query string in the conversion. Default is false.
3166 *
3167 * @return string The URL converted to lowercase.
3168 */
3169 function wpforo_url_strtolower( $url, $include_query = false ): string {
3170 // If $include_query is true, convert the entire URL to lowercase and return it.
3171 if( $include_query ) return strtolower( $url );
3172 // If the URL does not contain a query string, convert the entire URL to lowercase and return it.
3173 if( strpos( $url, '?' ) === false ) {
3174 $url = strtolower( $url );
3175 } else {
3176 // If the URL contains a query string, split the URL into two parts at the '?' character.
3177 $parts = explode( '?', $url );
3178 // Remove the last element from the $parts array and store it in $query.
3179 $query = array_pop( $parts );
3180 $parts = array_map( 'strtolower', $parts );
3181 $url = implode( '?', $parts ) . '?' . $query;
3182 }
3183
3184 return $url;
3185 }
3186
3187 function wpforo_fix_url( string $url ): string {
3188 $hash = '';
3189 $url = preg_replace_callback( '#(\#[^/]*)$#iu', function( $m ) use ( &$hash ) {
3190 $hash = $m[1];
3191
3192 return '';
3193 }, $url, 1 );
3194
3195 if( preg_match( '#^((?:https?:|s?ftp:)//)([^?&/=\s:]+)((?::\d+)?)([^\r\n]*)#iu', (string) $url, $match ) ) {
3196 $match[2] = wpforo_urlencode( $match[2] );
3197 $url = $match[1] . $match[2] . $match[3] . implode( '/', array_map( 'wpforo_urlencode', explode( '/', $match[4] ) ) );
3198 } else {
3199 $url = implode( '/', array_map( 'wpforo_urlencode', explode( '/', $url ) ) );
3200 }
3201
3202 return $url . $hash;
3203 }
3204
3205 function wpforo_is_domains_equal( $url1, $url2 ) {
3206 $domain1 = strtolower( str_replace( 'www.', '', parse_url( $url1, PHP_URL_HOST ) ) );
3207 $domain2 = strtolower( str_replace( 'www.', '', parse_url( $url2, PHP_URL_HOST ) ) );
3208
3209 return $domain1 === $domain2;
3210 }
3211
3212 function wpforo_is_url_internal( $url, $home_url = null ) {
3213 $url = trim( (string) $url );
3214 if( ! preg_match( '#^(?:https?:)?//#iu', (string) $url ) ) return true;
3215 if( ! $home_url ) $home_url = home_url();
3216 $home_url = trim( (string) $home_url );
3217 $url = preg_replace( '#^(https?://)?(www\.)?#iu', '', (string) $url );
3218 $home_url = preg_replace( [ '#^(https?://)?(www\.)?#iu', '#/?\?.*$#isu', '#index\.php/?#iu' ], '', (string) $home_url );
3219
3220 return strpos( (string) $url, $home_url ) === 0;
3221 }
3222
3223 function wpforo_is_url_external( $url, $home_url = null ) {
3224 return ! wpforo_is_url_internal( $url, $home_url );
3225 }
3226
3227 function wpforo_settype( $var, $type ) {
3228 $var_type = strtolower( gettype( $var ) );
3229 $type = strtolower( (string) $type );
3230 $allowed_types = [ 'bool', 'boolean', 'int', 'integer', 'double', 'real', 'float', 'string', 'array', 'object', 'null' ];
3231 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' ]
3232 ) ) && ! ( $var_type === 'array' && $type === 'string' ) ) {
3233 settype( $var, $type );
3234 }
3235
3236 return $var;
3237 }
3238
3239 function wpforo_array_args_cast( $array, $type ) {
3240 return array_map( function( $var ) use ( $type ) {
3241 return wpforo_settype( $var, $type );
3242 }, $array );
3243 }
3244
3245 function wpforo_array_args_cast_and_merge( $array, $default ) {
3246 $array = (array) $array;
3247 $default = (array) $default;
3248 foreach( $array as $key => $value ) {
3249 if( array_key_exists( $key, $default ) ) {
3250 $array[ $key ] = wpforo_settype( $value, gettype( $default[ $key ] ) );
3251 if( is_array( $array[ $key ] ) ) {
3252 $array[ $key ] = wpforo_array_args_cast_and_merge(
3253 $array[ $key ],
3254 $default[ $key ]
3255 );
3256 } #### do not open this comment until you have checkboxes on options settings pages
3257 }
3258 }
3259 $array += $default;
3260
3261 return $array;
3262 }
3263
3264 function wpforo_db_data_format( $var ) {
3265 switch( gettype( $var ) ) {
3266 case 'bool':
3267 case 'boolean':
3268 case 'int':
3269 case 'integer':
3270 return '%d';
3271 case 'double':
3272 case 'real':
3273 case 'float':
3274 return '%f';
3275 default:
3276 return '%s';
3277 }
3278 }
3279
3280 /**
3281 * @param int $seconds execution time in seconds , by default 0 (unlimited)
3282 */
3283 function wpforo_set_max_execution_time( $seconds = 0 ) {
3284 if( function_exists( 'set_time_limit' ) ) @set_time_limit( $seconds );
3285 if( function_exists( 'ini_set' ) ) @ini_set( 'max_execution_time', $seconds );
3286 }
3287
3288 /**
3289 * @param string $pattern
3290 * @param array $array
3291 *
3292 * @return array
3293 */
3294 function wpforo_preg_grep_recursive( $pattern, $array ) {
3295 $m = [];
3296 if( is_array( $array ) ) {
3297 foreach( $array as $key => $value ) {
3298 if( is_string( $value ) || is_numeric( $value ) ) {
3299 if( preg_match( $pattern, (string) $value ) ) $m[ $key ] = $value;
3300 } elseif( is_array( $value = wpforo_settype( $value, 'array' ) ) ) {
3301 if( $_m = wpforo_preg_grep_recursive( $pattern, $value ) ) $m[ $key ] = $_m;
3302 }
3303 }
3304 }
3305
3306 return $m;
3307 }
3308
3309 function wpforo_send_new_user_notifications( $userid, $notify = 'both' ) {
3310 if( wpforo_setting( 'email', 'disable_new_user_admin_notification' ) ) {
3311 if( $notify !== 'admin' ) wp_send_new_user_notifications( $userid, 'user' );
3312 } else {
3313 wp_send_new_user_notifications( $userid, $notify );
3314 }
3315 }
3316
3317 function wpforo_get_callbacks_for_action( $hook = '' ) {
3318 global $wp_filter;
3319 if( empty( $hook ) || ! isset( $wp_filter[ $hook ] ) ) return [];
3320
3321 return $wp_filter[ $hook ]->callbacks;
3322 }
3323
3324 /**
3325 * The optimized version of wordpress url_to_postid($url) function
3326 *
3327 * Examine a URL and try to determine the post ID it represents.
3328 *
3329 * Checks are supposedly from the hosted site blog.
3330 *
3331 * @param string $url Permalink to check.
3332 *
3333 * @return int Post ID, or 0 on failure.
3334 * @global WP $wp Current WordPress environment instance.
3335 *
3336 * @since 1.7.4
3337 *
3338 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
3339 */
3340 function _wpforo_wp_url_to_postid( $url ) {
3341 if( strpos( (string) $url, admin_url() ) !== false ) return 0;
3342
3343 $url_host = str_replace( 'www.', '', (string) parse_url( $url, PHP_URL_HOST ) );
3344 $home_url_host = str_replace( 'www.', '', (string) parse_url( home_url(), PHP_URL_HOST ) );
3345
3346 // Bail early if the URL does not belong to this site.
3347 if( $url_host && $url_host !== $home_url_host ) return 0;
3348
3349 // First, check to see if there is a 'p=N' or 'page_id=N' to match against.
3350 if( preg_match( '#[?&](p|page_id|post_id|post|attachment_id)=(\d+)#', (string) $url, $values ) ) {
3351 if( $id = absint( $values[2] ) ) return $id;
3352 }
3353
3354 global $wp_rewrite;
3355 if( $wp_rewrite ) {
3356 // Get rid of the #anchor.
3357 $url_split = explode( '#', $url );
3358 $url = $url_split[0];
3359
3360 // Get rid of URL ?query=string.
3361 $url_split = explode( '?', $url );
3362 $url = $url_split[0];
3363
3364 // Set the correct URL scheme.
3365 $scheme = parse_url( home_url(), PHP_URL_SCHEME );
3366 $url = set_url_scheme( $url, $scheme );
3367
3368 // Add 'www.' if it is absent and should be there.
3369 if( false !== strpos( home_url(), '://www.' ) && false === strpos( $url, '://www.' ) ) {
3370 $url = str_replace( '://', '://www.', $url );
3371 }
3372
3373 // Strip 'www.' if it is present and shouldn't be.
3374 if( false === strpos( home_url(), '://www.' ) ) {
3375 $url = str_replace( '://www.', '://', $url );
3376 }
3377
3378 if( trim( (string) $url, '/' ) === home_url() && 'page' == get_option( 'show_on_front' ) ) {
3379 $page_on_front = get_option( 'page_on_front' );
3380 if( $page_on_front && get_post( $page_on_front ) instanceof WP_Post ) return (int) $page_on_front;
3381 }
3382
3383 // Check to see if we are using rewrite rules.
3384 $rewrite = $wp_rewrite->wp_rewrite_rules();
3385
3386 // Not using rewrite rules, and 'p=N' and 'page_id=N' methods failed, so we're out of options.
3387 if( empty( $rewrite ) ) return 0;
3388
3389 // Strip 'index.php/' if we're not using path info permalinks.
3390 if( ! $wp_rewrite->using_index_permalinks() ) {
3391 $url = str_replace( $wp_rewrite->index . '/', '', $url );
3392 }
3393
3394 if( false !== strpos( trailingslashit( (string) $url ), home_url( '/' ) ) ) {
3395 // Chop off http://domain.com/[path].
3396 $url = str_replace( home_url(), '', $url );
3397 } else {
3398 // Chop off /path/to/blog.
3399 $home_path = parse_url( home_url( '/' ) );
3400 $home_path = isset( $home_path['path'] ) ? $home_path['path'] : '';
3401 $url = preg_replace( sprintf( '#^%s#', preg_quote( $home_path ) ), '', trailingslashit( (string) $url ) );
3402 }
3403
3404 // Trim leading and lagging slashes.
3405 $url = trim( (string) $url, '/' );
3406
3407 $request = $url;
3408 $post_type_query_vars = [];
3409
3410 foreach( get_post_types( [], 'objects' ) as $post_type => $t ) {
3411 if( ! empty( $t->query_var ) ) {
3412 $post_type_query_vars[ $t->query_var ] = $post_type;
3413 }
3414 }
3415
3416 // Look for matches.
3417 $request_match = $request;
3418 foreach( (array) $rewrite as $match => $query ) {
3419 if( preg_match( "#^$match#", (string) $request_match, $matches ) ) {
3420
3421 if( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)]/', (string) $query, $varmatch ) ) {
3422 // This is a verbose page match, let's check to be sure about it.
3423 $page = get_page_by_path( $matches[ $varmatch[1] ] );
3424 if( ! $page ) {
3425 continue;
3426 }
3427
3428 $post_status_obj = get_post_status_object( $page->post_status );
3429 if( ! $post_status_obj->public && ! $post_status_obj->protected && ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
3430 continue;
3431 }
3432 }
3433
3434 // Got a match.
3435 // Trim the query of everything up to the '?'.
3436 $query = preg_replace( '!^.+\?!', '', (string) $query );
3437
3438 // Substitute the substring matches into the query.
3439 $query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) );
3440
3441 // Filter out non-public query vars.
3442 global $wp;
3443 parse_str( $query, $query_vars );
3444 $query = [];
3445 foreach( (array) $query_vars as $key => $value ) {
3446 if( in_array( $key, $wp->public_query_vars ) ) {
3447 $query[ $key ] = $value;
3448 if( isset( $post_type_query_vars[ $key ] ) ) {
3449 $query['post_type'] = $post_type_query_vars[ $key ];
3450 $query['name'] = $value;
3451 }
3452 }
3453 }
3454
3455 // Resolve conflicts between posts with numeric slugs and date archive queries.
3456 $query = wp_resolve_numeric_slug_conflicts( $query );
3457
3458 // Do the query.
3459 if( $p = wpforo_bigintval( wpfval( $query, 'p' ) ) ) return $p;
3460 if( $page_id = wpforo_bigintval( wpfval( $query, 'page_id' ) ) ) return $page_id;
3461
3462 $pagename = wpfval( $query, 'pagename' );
3463 $name = wpfval( $query, 'name' );
3464 if( $pagename || $name ) {
3465 if( ! $slug = $pagename ) $slug = $name;
3466 $sql = "SELECT `ID` FROM `" . WPF()->db->posts . "`
3467 WHERE `post_status` = 'publish'
3468 AND `post_name` = %s";
3469 $post_types = (array) apply_filters( 'wpforo_wp_url_to_postid_post_types', [ 'page', 'post' ] );
3470 if( $post_types ) $sql .= " AND `post_type` IN('" . implode( "','", $post_types ) . "')";
3471 $sql = WPF()->db->prepare( $sql, $slug );
3472 $postid = WPF()->db->get_var( $sql );
3473 if( $postid = wpforo_bigintval( $postid ) ) {
3474 return $postid;
3475 } else {
3476 return 0;
3477 }
3478 } else {
3479 return 0;
3480 }
3481
3482 }
3483 }
3484 }
3485
3486 return 0;
3487 }
3488
3489 function wpforo_wp_url_to_postid( $url ) {
3490 return wpforo_ram_get( '_wpforo_wp_url_to_postid', $url );
3491 }
3492
3493 function wpforo_get_blog_content_types() {
3494 $post_types = get_post_types( [
3495 'public' => true,
3496 'publicly_queryable' => true,
3497 'show_ui' => true,
3498 'capability_type' => 'post',
3499 ] );
3500 $post_types['post'] = 'post';
3501 $post_types['page'] = 'page';
3502 unset( $post_types['attachment'] );
3503 $post_types = array_filter( array_keys( $post_types ) );
3504
3505 return apply_filters( 'wpforo_get_blog_content_types', $post_types );
3506 }
3507
3508 /**
3509 * @param string $css
3510 *
3511 * @return string
3512 */
3513 function wpforo_add_wrapper( $css ) {
3514 $css = preg_replace( '@(#(?:wpforo-wrap|wpfa_dialog|wpfa_dialog_wrap)[\s.#{:>+\[~,])@um', '#wpforo $1', (string) $css );
3515
3516 return preg_replace( '@(#(?:wpforo|wpforo-wrap|wpfa_dialog|wpfa_dialog_wrap)\s*)#wpforo[\s.#{:>+\[~,]@um', '$1', (string) $css );
3517 }
3518
3519 /**
3520 * @param string $css
3521 *
3522 * @return string
3523 */
3524 function wpforo_wrap_fix_in_css( $css ) {
3525 if( ( strpos( (string) $css, '#wpforo-wrap' ) !== false && strpos( (string) $css, '#wpforo #wpforo-wrap' ) === false ) || ( strpos( (string) $css, '#wpfa_dialog' ) !== false && strpos(
3526 (string) $css,
3527 '#wpforo #wpfa_dialog'
3528 ) === false ) ) {
3529 $css = wpforo_add_wrapper( $css );
3530 }
3531
3532 return $css;
3533 }
3534
3535 /**
3536 * @param string $csspath
3537 */
3538 function wpforo_wrap_fix_in_cssfile( $csspath ) {
3539 $csspath = wpforo_fix_dir_sep( $csspath );
3540 if( is_file( $csspath ) ) {
3541 $css = wpforo_get_file_content( $csspath );
3542 $css = wpforo_wrap_fix_in_css( $css );
3543 if( md5( $css ) !== md5_file( $csspath ) ) wpforo_write_file( $csspath, $css );
3544 }
3545 }
3546
3547 function wpforo_wrap_in_all_addons_css() {
3548 $csspaths = [
3549 '/wpforo-ad-manager/assets/css/style.css',
3550 '/wpforo-advanced-attachments/assets/css/style.css',
3551 '/wpforo-cross-posting/assets/css/wpdiscuz-uploader.css',
3552 '/wpforo-cross-posting/assets/css/wpforo-cross-rtl.css',
3553 '/wpforo-cross-posting/assets/css/wpforo-cross.css',
3554 '/wpforo-embeds/assets/css/embed.css',
3555 '/wpforo-emoticons/assets/emoticons.css',
3556 '/wpforo-mycred/css/mycread.css',
3557 '/wpforo-polls/assets/css/poll.css',
3558 '/wpforo-private-messages/assets/css/style-rtl.css',
3559 '/wpforo-private-messages/assets/css/style.css',
3560 '/wpforo-user-custom-fields/assets/css/frontend.css',
3561 ];
3562 foreach( $csspaths as $csspath ) wpforo_wrap_fix_in_cssfile( WP_PLUGIN_DIR . $csspath );
3563
3564 WPF()->dissmissed['addons_css_update'] = 1;
3565 wpforo_update_option( 'dissmissed', WPF()->dissmissed );
3566 }
3567
3568 /*add_action( 'admin_notices', function() {
3569 if( ! (int) wpfval( WPF()->dissmissed, 'addons_css_update' ) ) {
3570 $has_addon = false;
3571 foreach( wpforo_get_addons_info() as $addon ) {
3572 if( class_exists( $addon['class'] ) ) {
3573 $has_addon = true;
3574 break;
3575 }
3576 }
3577 if( ! $has_addon ) return;
3578 $class = 'notice notice-warning';
3579 $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>';
3580 $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>';
3581 printf( '<div class="%1$s"><p>%2$s</p></div>', $class, $message );
3582 }
3583 } );*/
3584
3585 /**
3586 * @param string $url
3587 *
3588 * @return string|null
3589 */
3590 function wpforo_get_topic_slug_from_url( $url = '' ) {
3591 $url = trim( (string) $url );
3592 if( ! $url ) $url = wpforo_get_request_uri();
3593
3594 if( is_wpforo_url( $url ) ) {
3595 if( preg_match( '#/' . preg_quote( wpforo_settings_get_slug( 'postid' ) ) . '/(\d+)/?$#isu', strtok( $url, '?' ), $match ) ) {
3596 $url = WPF()->post->get_url( $match[1] );
3597 }
3598 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 ) ) {
3599 return $match[1];
3600 }
3601 }
3602
3603 return null;
3604 }
3605
3606 function wpforo_clean_folder( $directory ) {
3607 $directory = wpforo_fix_dir_sep( $directory );
3608 $directory_ns = trim( (string) $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR . '*';
3609 $directory_ws = DIRECTORY_SEPARATOR . trim( (string) $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR . '*';
3610 $glob = glob( $directory_ns );
3611 if( empty( $glob ) ) $glob = glob( $directory_ws );
3612 foreach( $glob as $item ) {
3613 if( strpos( (string) $item, 'index.html' ) !== false || strpos( (string) $item, '.htaccess' ) !== false ) continue;
3614 if( ! is_dir( $item ) && file_exists( $item ) ) {
3615 @unlink( $item );
3616 }
3617 }
3618 }
3619
3620 /**
3621 * @param string $basename
3622 *
3623 * @return string
3624 */
3625 function wpforo_fix_table_name( $basename ) {
3626 return WPF()->fix_table_name( $basename );
3627 }
3628
3629 /**
3630 * @param callable $func
3631 * @param mixed ... $_, ... [optional] call_user_func parameters
3632 *
3633 * @return mixed
3634 */
3635 function wpforo_ram_get( $func ) {
3636 return call_user_func_array( [ WPF()->ram_cache, 'call_user_func' ], func_get_args() );
3637 }
3638
3639 function wpforo_prefix( $str = '', $wp_prefix = false ) {
3640 return ( $wp_prefix ? WPF()->blog_prefix : '' ) . WPF()->prefix . $str;
3641 }
3642
3643 function wpforo_prefix_slug( $str = '' ) {
3644 if( $str === 'dashboard' && ! is_wpforo_multiboard() ) $str = 'overview';
3645
3646 return str_replace( '_', '-', wpforo_prefix( $str ) );
3647 }
3648
3649 function wpforo_get_ajax_url() {
3650 return admin_url(
3651 sprintf(
3652 'admin-ajax.php?lang=%1$s&page_id=%2$d&wpforo_boardid=%3$d',
3653 strtok( WPF()->locale, '_' ),
3654 WPF()->board->get_current( 'pageid' ),
3655 WPF()->board->get_current( 'boardid' )
3656 )
3657 );
3658 }
3659
3660 function wpforo_get_query_var_lang() {
3661 if( ! $lang = wpfval( $_GET, 'lang' ) ) $lang = get_query_var( 'lang' );
3662
3663 return $lang;
3664 }
3665
3666 function wpforo_is_db_mysql8() {
3667 return wpforo_ram_get( [ WPF(), 'is_db_mysql8' ] );
3668 }
3669
3670 function wpforo_is_module_enabled( $module_key, $board = [] ) {
3671 return WPF()->board->is_module_enabled( $module_key, $board );
3672 }
3673
3674 function is_wpforo_multiboard() {
3675 return wpforo_ram_get( [ WPF()->board, 'is_multi' ] );
3676 }
3677
3678 function wpforo_get_redirect_to() {
3679 if( ! ( ( $redirect_to = urldecode( (string) wpfval( $_GET, 'redirect_to' ) ) ) && wpforo_is_url_internal( $redirect_to ) ) ) {
3680 $redirect_to = preg_replace( '#/?\?.*$#isu', '', wpforo_get_request_uri() );
3681 }
3682
3683 return $redirect_to;
3684 }
3685
3686 function wpforo_get_redirect_to_url_path( $redirect_to = null ) {
3687 if( is_null( $redirect_to ) ) $redirect_to = wpforo_get_redirect_to();
3688
3689 return $redirect_to ? '?redirect_to=' . urlencode( (string) $redirect_to ) : '';
3690 }
3691
3692 function wpforo_login_url( $redirect_to = null ): string {
3693 if( wpforo_setting( 'authorization', 'login_url' ) ) {
3694 $login_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'login_url' ), '/' );
3695 } else {
3696 $login_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'login' );
3697 }
3698 $login_url = apply_filters( 'wpforo_login_url', $login_url );
3699
3700 return esc_url_raw( $login_url );
3701 }
3702
3703 function wpforo_register_url( $redirect_to = null ): string {
3704 if( wpforo_setting( 'authorization', 'register_url' ) ) {
3705 $register_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'register_url' ), '/' );
3706 } else {
3707 $register_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'register' );
3708 }
3709 $register_url = apply_filters( 'wpforo_register_url', $register_url );
3710
3711 return esc_url_raw( $register_url );
3712 }
3713
3714 function wpforo_logout_url( $redirect_to = null ): string {
3715 $logout_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'logout' );
3716 $logout_url = wp_nonce_url( $logout_url, 'wpforo_logout', '_wpfnonce' );
3717 $logout_url = apply_filters( 'wpforo_logout_url', $logout_url );
3718
3719 return esc_url_raw( $logout_url );
3720 }
3721
3722 function wpforo_lostpassword_url( $redirect_to = null ): string {
3723 if( wpforo_setting( 'authorization', 'lost_password_url' ) ) {
3724 $lostpassword_url = trim( (string) get_bloginfo( 'url' ), '/' ) . '/' . ltrim( (string) wpforo_setting( 'authorization', 'lost_password_url' ), '/' );
3725 } else {
3726 if( wpforo_setting( 'authorization', 'use_our_lostpassword_url' ) ) {
3727 $lostpassword_url = wpforo_url( wpforo_get_redirect_to_url_path( $redirect_to ), 'lostpassword' );
3728 } else {
3729 if( is_null( $redirect_to ) ) $redirect_to = wpforo_get_redirect_to();
3730 $lostpassword_url = wp_lostpassword_url( $redirect_to );
3731 }
3732 }
3733 $lostpassword_url = apply_filters( 'wpforo_lostpassword_url', $lostpassword_url );
3734
3735 return esc_url_raw( $lostpassword_url );
3736 }
3737
3738 function wpforo_resetpassword_url( $rp_key, $rp_login ): string {
3739 $rp_key = rawurlencode( rawurldecode( $rp_key ) );
3740 $rp_login = rawurlencode( rawurldecode( $rp_login ) );
3741 $resetpassword_url = wpforo_url( "?wpfaction=resetpassword_form&rp_key=$rp_key&rp_login=$rp_login", 'lostpassword' );
3742 $resetpassword_url = apply_filters( 'wpforo_resetpassword_url', $resetpassword_url );
3743
3744 return esc_url_raw( $resetpassword_url );
3745 }
3746
3747 function wpforo_members_url(): string {
3748 return wpforo_url( '', 'members' );
3749 }
3750
3751 function wpforo_redirect_to() {
3752 $redirect_url = wpforo_home_url();
3753 if( ( $redirect_to = urldecode( (string) wpfval( $_GET, 'redirect_to' ) ) ) && wpforo_is_url_internal( $redirect_to ) && ( ! WPF()->current_userid || ( strpos(
3754 $redirect_to,
3755 wpforo_settings_get_slug( 'login' )
3756 ) === false && strpos(
3757 $redirect_to,
3758 wpforo_settings_get_slug(
3759 'register'
3760 )
3761 ) === false && strpos(
3762 $redirect_to,
3763 wpforo_settings_get_slug(
3764 'lostpassword'
3765 )
3766 ) === false ) ) ) {
3767 $redirect_url = $redirect_to;
3768 }
3769 wp_safe_redirect( $redirect_url );
3770 exit();
3771 }
3772
3773 function wpforo_settings_get_slug( $key ): string {
3774 $slug = wpfval( WPF()->settings->slugs, $key );
3775 if( ! $slug ) $slug = $key;
3776
3777 return strtolower( (string) $slug );
3778 }
3779
3780 function wpforo_settings_get_slug_key( $slug ) {
3781 $ret = array_search( $slug, WPF()->settings->slugs );
3782 if( $ret === false ) $ret = $slug;
3783
3784 return $ret;
3785 }
3786
3787 function wpforo_is_slug_base( $slug ): bool {
3788 return ! wpfkey( WPF()->tpl->templates, wpforo_settings_get_slug_key( $slug ) );
3789 }
3790
3791 /**
3792 * @param string $group
3793 * @param string|int ...$name ... $_, ... [optional] more keys
3794 *
3795 * @return mixed|null
3796 */
3797 function wpforo_setting( $group, $name ) {
3798 if( ! is_null( WPF()->settings ) && property_exists( WPF()->settings, $group ) ) {
3799 $args = func_get_args();
3800 $args[0] = &WPF()->settings->$group;
3801
3802 return call_user_func_array( 'wpfval', $args );
3803 }
3804
3805 return null;
3806 }
3807
3808 function _wpforo_apply_email_shortcodes( $txt, $args ) {
3809 $forumid = (int) wpfval( $args, 'forumid' );
3810 $topicid = wpforo_bigintval( wpfval( $args, 'topicid' ) );
3811 $postid = wpforo_bigintval( wpfval( $args, 'postid' ) );
3812 $user = (array) wpfval( $args, 'user' );
3813 $owner = (array) wpfval( $args, 'owner' );
3814 $unsubscribe_link = (string) wpfval( $args, 'unsubscribe_link' );
3815
3816 $txt = do_shortcode( $txt );
3817
3818 return preg_replace_callback( '#\[[^\[\]]+?]#iu', function( $match ) use ( $forumid, $topicid, $postid, $user, $owner, $unsubscribe_link ) {
3819 $body_length = apply_filters( 'wpforo_email_notification_post_body_length', 1000 );
3820 $value = '';
3821 $shortcode = $match[0];
3822
3823 if( $shortcode === '[unsubscribe_link]' ) {
3824 if( $unsubscribe_link ) {
3825 $value = sprintf( '<br><a target="_blank" href="%1$s">%2$s</a>', esc_url( (string) $unsubscribe_link ), wpforo_phrase( 'Unsubscribe', false ) );
3826 $value = stripslashes( $value );
3827 }
3828 } elseif( strpos( (string) $shortcode, '[user_' ) === 0 ) {
3829 if( wpforo_bigintval( wpfval( $user, 'userid' ) ) ) {
3830 $value = wpforo_user_field_shortcode_to_value( $shortcode, $user['userid'] );
3831 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3832 $value = '';
3833 }
3834 } else {
3835 if( $shortcode === '[user_display_name]' ) {
3836 $value = wpfval( $user, 'display_name' ) ?: wpforo_phrase( 'Anonymous', false );
3837 } elseif( $shortcode === '[user_user_email]' ) {
3838 $value = (string) wpfval( $user, 'user_email' );
3839 }
3840 }
3841 } elseif( strpos( (string) $shortcode, '[owner_' ) === 0 ) {
3842 if( wpforo_bigintval( wpfval( $owner, 'userid' ) ) ) {
3843 $value = wpforo_user_field_shortcode_to_value( $shortcode, $owner['userid'] );
3844 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3845 $value = '';
3846 }
3847 } else {
3848 if( $shortcode === '[owner_display_name]' ) {
3849 $value = wpfval( $owner, 'display_name' ) ?: wpforo_phrase( 'Anonymous', false );
3850 } elseif( $shortcode === '[owner_user_email]' ) {
3851 $value = (string) wpfval( $owner, 'user_email' );
3852 }
3853 }
3854 } elseif( strpos( (string) $shortcode, '[forum_' ) === 0 ) {
3855 if( $forumid ) {
3856 $value = wpforo_forum_field_shortcode_to_value( $shortcode, $forumid );
3857 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3858 $value = '';
3859 }
3860 }
3861 } elseif( strpos( (string) $shortcode, '[topic_' ) === 0 ) {
3862 if( $topicid ) {
3863 $value = wpforo_topic_field_shortcode_to_value( $shortcode, $topicid );
3864 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3865 $value = '';
3866 } elseif( $shortcode === '[topic_body]' ) {
3867 $value = wpforo_text( wpforo_kses( wpforo_content_filter( $value ), 'email' ), $body_length, false, false, false, true, false );
3868 }
3869 }
3870 } elseif( strpos( (string) $shortcode, '[post_' ) === 0 ) {
3871 if( $postid ) {
3872 $value = wpforo_post_field_shortcode_to_value( $shortcode, $postid );
3873 if( ! $value || ! ( is_string( $value ) || is_numeric( $value ) ) ) {
3874 $value = '';
3875 } elseif( $shortcode === '[post_body]' ) {
3876 $value = wpforo_text( wpforo_kses( wpforo_content_filter( $value ), 'email' ), $body_length, false, false, false, true, false );
3877 }
3878 }
3879 }
3880
3881 return $value;
3882 }, (string) $txt );
3883 }
3884
3885 function wpforo_apply_email_shortcodes( $txt, $pitem, $item, $owner, $user, $unsubscribe_link ) {
3886 $forumid = ( intval( wpfval( $pitem, 'forumid' ) ) ?: intval( wpfval( $item, 'forumid' ) ) );
3887 $topicid = ( wpforo_bigintval( wpfval( $pitem, 'topicid' ) ) ?: wpforo_bigintval( wpfval( $item, 'topicid' ) ) );
3888 $postid = ( wpforo_bigintval( wpfval( $item, 'postid' ) ) ?: wpforo_bigintval( wpfval( $item, 'first_postid' ) ) );
3889
3890 return _wpforo_apply_email_shortcodes( $txt, compact( 'forumid', 'topicid', 'postid', 'owner', 'user', 'unsubscribe_link' ) );
3891 }
3892
3893 function wpforo_send_email( $email, $sbj, $msg, $headers = '', $context = 'general', $related_id = 0 ) {
3894 if( defined( 'IS_GO2WPFORO' ) && IS_GO2WPFORO ) return false;
3895 if( apply_filters( 'break_wpforo_send_email', false, $email, $sbj, $msg, $headers ) ) return false;
3896 $key = [ $email, $sbj, substr( $msg, 0, 100 ) ];
3897 if( WPF()->ram_cache->exists( $key ) ) return false;
3898
3899 if( isset( WPF()->email_queue ) ) {
3900 $result = WPF()->email_queue->queue_or_send( $email, $sbj, $msg, $headers, $context, $related_id );
3901 } else {
3902 add_filter( 'wp_mail_content_type', 'wpforo_set_html_content_type', 999 );
3903 $result = wp_mail( $email, $sbj, $msg, ( $headers ?: wpforo_mail_headers() ) );
3904 remove_filter( 'wp_mail_content_type', 'wpforo_set_html_content_type' );
3905 }
3906
3907 if( $result ) {
3908 WPF()->ram_cache->set( $key, true );
3909 return true;
3910 }
3911
3912 return false;
3913 }
3914
3915 function wpforo_get_site_default_locale() {
3916 $locale = trim( (string) get_option( 'WPLANG', 'en_US' ) );
3917 if( ! $locale ) $locale = 'en_US';
3918
3919 return $locale;
3920 }
3921
3922 function wpforo_sanitize( $action, $variable_name, $filter, $default = '' ) {
3923 if( $filter === "FILTER_SANITIZE_STRING" ) {
3924 $glob = INPUT_POST === $action ? $_POST : $_GET;
3925 if( wpfkey( $glob, $variable_name ) ) {
3926 return sanitize_text_field( $glob[ $variable_name ] );
3927 } else {
3928 return $default;
3929 }
3930 }
3931 $variable = isset( $variable_name ) ? filter_input( $action, $variable_name, $filter ) : '';
3932
3933 return $variable ?: $default;
3934 }
3935
3936 function wpforo_length( $str, $filtered = true ) {
3937 $str = $filtered ? strip_tags( trim( (string) $str ) ) : $str;
3938 if( function_exists( 'mb_substr' ) ) {
3939 $length = mb_strlen( (string) $str, get_option( 'blog_charset' ) );
3940 } else {
3941 $length = strlen( (string) $str );
3942 }
3943
3944 return (int) $length;
3945 }
3946
3947 if( ! function_exists( 'wpforo_remote_retrieve_header' ) ) {
3948 /**
3949 *
3950 * @param array|WP_Error $response HTTP response.
3951 * @param string $header Header name to retrieve value from.
3952 *
3953 * @return string string The header value. Empty string on if incorrect parameter given, or if the header doesn't exist.
3954 */
3955 function wpforo_remote_retrieve_header( $response, $header ) {
3956 $header = wp_remote_retrieve_header( $response, $header );
3957 if( is_array( $header ) ) $header = (string) array_pop( $header );
3958
3959 return $header;
3960 }
3961 }
3962
3963 function wpforo_is_array_of_scalars( $array ) {
3964 if( ! is_array( $array ) ) return false;
3965 foreach( $array as $element ) if( ! is_scalar( $element ) ) return false;
3966
3967 return true;
3968 }
3969
3970 function wpforo_is_gzipped( $contents ): bool {
3971 // phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found
3972 if( function_exists( 'mb_strpos' ) ) {
3973 return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
3974 } else {
3975 return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
3976 }
3977 // phpcs:enable
3978 }
3979
3980 function wpforo_sanitize_svg( $file ): bool {
3981 $dirty = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3982
3983 // Is the SVG gzipped? If so we try and decode the string
3984 $is_zipped = wpforo_is_gzipped( $dirty );
3985 if( $is_zipped ) {
3986 $dirty = gzdecode( $dirty );
3987
3988 // If decoding fails, bail as we're not secure
3989 if( false === $dirty ) {
3990 return false;
3991 }
3992 }
3993
3994 $clean = trim( wpforo_kses( $dirty, 'svg' ) );
3995
3996 if( ! $clean ) {
3997 return false;
3998 }
3999
4000 // If we were gzipped, we need to re-zip
4001 if( $is_zipped ) {
4002 $clean = gzencode( $clean );
4003 }
4004
4005 file_put_contents( $file, $clean ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
4006
4007 return true;
4008 }
4009
4010 function wpforo_check_for_svg( $file ) {
4011 // Ensure we have a proper file path before processing
4012 if( ! isset( $file['tmp_name'] ) ) {
4013 return $file;
4014 }
4015
4016 $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], ( $file['name'] ?? '' ), [] );
4017 $type = ! empty( $wp_filetype['type'] ) ? $wp_filetype['type'] : (string) wpfval( $file, 'type' );
4018
4019 if( in_array( $type, [ 'image/svg+xml', 'image/svg' ], true ) ) {
4020 if( ! wpforo_sanitize_svg( $file['tmp_name'] ) ) {
4021 $file['error'] = __(
4022 "Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded",
4023 'wpforo'
4024 );
4025 }
4026 }
4027
4028 return $file;
4029 }
4030
4031
4032 /**
4033 * Custom function to replace is_numeric() function avoiding negative
4034 * numbers to be considered as numeric. This is mostly used differentiating
4035 * between ID and slugs. Sometimes slugs can be like "-12345".
4036 *
4037 * @param mixed $value
4038 *
4039 * @return bool
4040 */
4041 function wpforo_is_id( $value ) {
4042 return is_numeric( $value ) && (int) $value >= 0;
4043 }
4044
4045 function is_avatar_url( string $url ): bool {
4046 // Check if the string is a valid URL
4047 if( filter_var( $url, FILTER_VALIDATE_URL ) ) {
4048 // Make a HEAD request to check the Content-Type
4049 $response = wp_safe_remote_head( $url );
4050
4051 if( ! is_wp_error( $response ) ) {
4052 $content_type = wp_remote_retrieve_header( $response, 'content-type' );
4053
4054 // Check if the Content-Type is an image
4055 if( strpos( $content_type, 'image/' ) !== false ) {
4056 return true; // The URL points to an image
4057 }
4058 }
4059 }
4060
4061 return false; // Not a valid avatar URL
4062 }
4063
4064 /**
4065 * Function to generate image object for DiscussionForumPosting schema JSON-LD
4066 *
4067 * @param string $content
4068 *
4069 * @return string
4070 */
4071 function wpforo_generate_scheme_image_object( $content ) {
4072 $image = '';
4073 if( function_exists( 'WPF_ATTACH' ) ) {
4074 $content = WPF_ATTACH()->tools->do_shortcodes( $content );
4075 }
4076 # TODO: Add support for secure URLs w/o image extensions
4077 # Currently, only URLs with image extensions are supported
4078 # by wpforo_find_image_urls() function.
4079 $images = wpforo_find_image_urls( $content, false );
4080 if( ! empty( $images ) ) {
4081 $image = '"image": [';
4082 foreach( $images as $img ) {
4083 $image .= '{ "@type": "ImageObject", "url": "' . esc_url_raw( $img ) . '"},';
4084 }
4085 $image = trim( $image, ',' ) . '],';
4086 }
4087
4088 return $image;
4089 }
4090
4091 function wpforo_generate_scheme_author_object( $user_id ) {
4092 $author = '';
4093 $post_member = wpforo_member( $user_id );
4094 $author_posts = wpfval( $post_member, 'posts' ) ? $post_member['posts'] : 1;
4095 $author_name = wpfval( $post_member, 'display_name' ) ? $post_member['display_name'] : 'Guest';
4096 $author_url = wpfval( $post_member, 'profile_url' ) ? '
4097 "url": "' . esc_url_raw( $post_member['profile_url'] ) . '",' : '';
4098 $author = ',
4099 "author": {
4100 "@type": "Person",
4101 "name": "' . esc_attr( $author_name ) . '",' . $author_url . '
4102 "agentInteractionStatistic": {
4103 "@type": "InteractionCounter",
4104 "interactionType": "https://schema.org/WriteAction",
4105 "userInteractionCount": ' . intval( $author_posts ) . '
4106 }
4107 }';
4108
4109 return $author;
4110 }
4111
4112 /**
4113 * Get wpForo home URL for a specific language/locale
4114 *
4115 * @param string $language Language code (e.g., 'de_DE', 'en_US', 'fr_FR')
4116 * @return string Forum home URL for the specified language
4117 */
4118 function wpforo_get_home_url_for_language( $language ) {
4119 $sql = WPF()->db->prepare(
4120 "SELECT * FROM " . WPF()->tables->boards . " WHERE status = 1 AND locale = %s LIMIT 1",
4121 sanitize_locale_name($language)
4122 );
4123 $board = (array) WPF()->db->get_row( $sql, ARRAY_A );
4124 if( $board ) $board = WPF()->board->decode( $board );
4125
4126 if( !$board ) {
4127 return home_url(); // Fallback to default
4128 }
4129 return trailingslashit( home_url( $board['slug'] ) );
4130 }
4131
4132 function wpforo_get_current_language_url( $url = '' ) {
4133 $url = ( $url ) ? $url : '';
4134 if( function_exists( 'pll_current_language' ) && $language = pll_current_language( 'locale' ) ) {
4135 $sql = WPF()->db->prepare(
4136 "SELECT * FROM " . WPF()->tables->boards . " WHERE status = 1 AND locale = %s LIMIT 1",
4137 sanitize_locale_name($language)
4138 );
4139 $board = (array) WPF()->db->get_row( $sql, ARRAY_A );
4140 if( $boardid = wpfval($board, 'boardid') ){
4141 $url = ( $url ) ? $url : $_SERVER['REQUEST_URI'];
4142 $url = add_query_arg( 'boardid', intval( $boardid ), $_SERVER['REQUEST_URI'] );
4143 }
4144 }
4145 return $url;
4146 }
4147
4148 /**
4149 * Centralized logging function for wpForo AI features.
4150 *
4151 * Nothing is logged unless wpforo General > Debug Mode is enabled
4152 * OR WordPress WP_DEBUG is on. This ensures zero disk usage on production sites.
4153 *
4154 * Log levels: 'error', 'info', 'debug'
4155 * - error: Logged when WP_DEBUG is on OR wpforo Debug Mode is on
4156 * - info: Logged only when BOTH WP_DEBUG and wpforo Debug Mode are on
4157 * - debug: Logged when wpforo Debug Mode is on (with or without WP_DEBUG)
4158 *
4159 * Logs are written to: wp-content/uploads/wpforo/ai-logs/{Y-m-d}.log
4160 *
4161 * @param string $level Log level: 'error', 'info', or 'debug'
4162 * @param string $message Log message
4163 * @param string $tag Component tag, e.g. 'Moderation', 'Client', 'VectorStorage'
4164 */
4165 function wpforo_ai_log( $level, $message, $tag = '' ) {
4166 static $enabled_levels = null;
4167 static $log_dir = null;
4168
4169 // Resolve enabled levels once per request
4170 if ( $enabled_levels === null ) {
4171 $enabled_levels = [];
4172 $wpforo_debug = wpforo_setting( 'general', 'debug_mode' );
4173 $wp_debug = defined( 'WP_DEBUG' ) && WP_DEBUG;
4174 if ( $wp_debug || $wpforo_debug ) {
4175 $enabled_levels[] = 'error';
4176 }
4177 if ( $wpforo_debug ) {
4178 $enabled_levels[] = 'debug';
4179 }
4180 if ( $wp_debug && $wpforo_debug ) {
4181 $enabled_levels[] = 'info';
4182 }
4183 }
4184
4185 if ( ! in_array( $level, $enabled_levels, true ) ) {
4186 return;
4187 }
4188
4189 // Resolve log directory once per request
4190 if ( $log_dir === null ) {
4191 $upload_dir = wp_upload_dir();
4192 $log_dir = $upload_dir['basedir'] . '/wpforo/ai-logs';
4193 if ( ! is_dir( $log_dir ) ) {
4194 wp_mkdir_p( $log_dir );
4195 @file_put_contents( $log_dir . '/index.php', "<?php // Silence is golden.\n" );
4196 }
4197 }
4198
4199 $prefix = '[wpForo AI' . ( $tag ? ' ' . $tag : '' ) . ']';
4200 $date = gmdate( 'Y-m-d H:i:s' );
4201 $entry = sprintf( "[%s] [%s] %s %s\n", $date, strtoupper( $level ), $prefix, $message );
4202
4203 $log_file = $log_dir . '/' . gmdate( 'Y-m-d' ) . '.log';
4204 @file_put_contents( $log_file, $entry, FILE_APPEND | LOCK_EX );
4205 }
4206
4207 /**
4208 * Get the master list of AI-supported languages.
4209 *
4210 * Single source of truth for all AI language lists across the plugin:
4211 * - Admin settings dropdowns (Settings.php)
4212 * - Frontend widget preference dropdown (header.php)
4213 * - Language name resolution (AIClient.php get_user_language)
4214 * - Preference validation whitelist (AIClient.php ajax_save_ai_preferences)
4215 *
4216 * Each entry: [ 'code' => '2-letter ISO code', 'locale' => 'WordPress locale', 'name' => 'English name', 'native' => 'Native name' ]
4217 *
4218 * @return array
4219 */
4220 function wpforo_get_ai_languages() {
4221 return [
4222 // Major world languages
4223 [ 'code' => 'en', 'locale' => 'en_US', 'name' => 'English', 'native' => 'English' ],
4224 [ 'code' => 'en', 'locale' => 'en_GB', 'name' => 'English (UK)', 'native' => 'English (UK)' ],
4225 [ 'code' => 'zh', 'locale' => 'zh_CN', 'name' => 'Chinese Simplified', 'native' => '简体中文' ],
4226 [ 'code' => 'zh', 'locale' => 'zh_TW', 'name' => 'Chinese Traditional', 'native' => '繁體中文' ],
4227 [ 'code' => 'es', 'locale' => 'es_ES', 'name' => 'Spanish', 'native' => 'Español' ],
4228 [ 'code' => 'hi', 'locale' => 'hi_IN', 'name' => 'Hindi', 'native' => 'हिन्दी' ],
4229 [ 'code' => 'ar', 'locale' => 'ar', 'name' => 'Arabic', 'native' => 'العربية' ],
4230 [ 'code' => 'pt', 'locale' => 'pt_BR', 'name' => 'Portuguese (Brazil)', 'native' => 'Português Brasil' ],
4231 [ 'code' => 'pt', 'locale' => 'pt_PT', 'name' => 'Portuguese', 'native' => 'Português' ],
4232 [ 'code' => 'bn', 'locale' => 'bn_BD', 'name' => 'Bengali', 'native' => 'বাংলা' ],
4233 [ 'code' => 'ru', 'locale' => 'ru_RU', 'name' => 'Russian', 'native' => 'Русский' ],
4234 [ 'code' => 'ja', 'locale' => 'ja', 'name' => 'Japanese', 'native' => '日本語' ],
4235 [ 'code' => 'fr', 'locale' => 'fr_FR', 'name' => 'French', 'native' => 'Français' ],
4236 [ 'code' => 'de', 'locale' => 'de_DE', 'name' => 'German', 'native' => 'Deutsch' ],
4237 [ 'code' => 'ko', 'locale' => 'ko_KR', 'name' => 'Korean', 'native' => '한국어' ],
4238 [ 'code' => 'it', 'locale' => 'it_IT', 'name' => 'Italian', 'native' => 'Italiano' ],
4239 [ 'code' => 'tr', 'locale' => 'tr_TR', 'name' => 'Turkish', 'native' => 'Türkçe' ],
4240 [ 'code' => 'vi', 'locale' => 'vi_VN', 'name' => 'Vietnamese', 'native' => 'Tiếng Việt' ],
4241 [ 'code' => 'pl', 'locale' => 'pl_PL', 'name' => 'Polish', 'native' => 'Polski' ],
4242 [ 'code' => 'nl', 'locale' => 'nl_NL', 'name' => 'Dutch', 'native' => 'Nederlands' ],
4243 [ 'code' => 'th', 'locale' => 'th', 'name' => 'Thai', 'native' => 'ไทย' ],
4244 [ 'code' => 'id', 'locale' => 'id_ID', 'name' => 'Indonesian', 'native' => 'Bahasa Indonesia' ],
4245 [ 'code' => 'ms', 'locale' => 'ms_MY', 'name' => 'Malay', 'native' => 'Bahasa Melayu' ],
4246 // European languages
4247 [ 'code' => 'uk', 'locale' => 'uk', 'name' => 'Ukrainian', 'native' => 'Українська' ],
4248 [ 'code' => 'ro', 'locale' => 'ro_RO', 'name' => 'Romanian', 'native' => 'Română' ],
4249 [ 'code' => 'el', 'locale' => 'el_GR', 'name' => 'Greek', 'native' => 'Ελληνικά' ],
4250 [ 'code' => 'cs', 'locale' => 'cs_CZ', 'name' => 'Czech', 'native' => 'Čeština' ],
4251 [ 'code' => 'sv', 'locale' => 'sv_SE', 'name' => 'Swedish', 'native' => 'Svenska' ],
4252 [ 'code' => 'hu', 'locale' => 'hu_HU', 'name' => 'Hungarian', 'native' => 'Magyar' ],
4253 [ 'code' => 'da', 'locale' => 'da_DK', 'name' => 'Danish', 'native' => 'Dansk' ],
4254 [ 'code' => 'fi', 'locale' => 'fi', 'name' => 'Finnish', 'native' => 'Suomi' ],
4255 [ 'code' => 'no', 'locale' => 'no_NO', 'name' => 'Norwegian', 'native' => 'Norsk' ],
4256 [ 'code' => 'sk', 'locale' => 'sk_SK', 'name' => 'Slovak', 'native' => 'Slovenčina' ],
4257 [ 'code' => 'bg', 'locale' => 'bg_BG', 'name' => 'Bulgarian', 'native' => 'Български' ],
4258 [ 'code' => 'hr', 'locale' => 'hr', 'name' => 'Croatian', 'native' => 'Hrvatski' ],
4259 [ 'code' => 'sr', 'locale' => 'sr_RS', 'name' => 'Serbian', 'native' => 'Српски' ],
4260 [ 'code' => 'sl', 'locale' => 'sl_SI', 'name' => 'Slovenian', 'native' => 'Slovenščina' ],
4261 [ 'code' => 'lt', 'locale' => 'lt_LT', 'name' => 'Lithuanian', 'native' => 'Lietuvių' ],
4262 [ 'code' => 'lv', 'locale' => 'lv', 'name' => 'Latvian', 'native' => 'Latviešu' ],
4263 [ 'code' => 'et', 'locale' => 'et', 'name' => 'Estonian', 'native' => 'Eesti' ],
4264 [ 'code' => 'ca', 'locale' => 'ca', 'name' => 'Catalan', 'native' => 'Català' ],
4265 [ 'code' => 'gl', 'locale' => 'gl_ES', 'name' => 'Galician', 'native' => 'Galego' ],
4266 [ 'code' => 'eu', 'locale' => 'eu', 'name' => 'Basque', 'native' => 'Euskara' ],
4267 [ 'code' => 'sq', 'locale' => 'sq', 'name' => 'Albanian', 'native' => 'Shqip' ],
4268 [ 'code' => 'mk', 'locale' => 'mk_MK', 'name' => 'Macedonian', 'native' => 'Македонски' ],
4269 [ 'code' => 'bs', 'locale' => 'bs_BA', 'name' => 'Bosnian', 'native' => 'Bosanski' ],
4270 [ 'code' => 'mt', 'locale' => 'mt_MT', 'name' => 'Maltese', 'native' => 'Malti' ],
4271 [ 'code' => 'is', 'locale' => 'is_IS', 'name' => 'Icelandic', 'native' => 'Íslenska' ],
4272 [ 'code' => 'ga', 'locale' => 'ga', 'name' => 'Irish', 'native' => 'Gaeilge' ],
4273 [ 'code' => 'cy', 'locale' => 'cy', 'name' => 'Welsh', 'native' => 'Cymraeg' ],
4274 [ 'code' => 'be', 'locale' => 'be_BY', 'name' => 'Belarusian', 'native' => 'Беларуская' ],
4275 [ 'code' => 'lb', 'locale' => 'lb_LU', 'name' => 'Luxembourgish', 'native' => 'Lëtzebuergesch' ],
4276 // Middle Eastern & Central Asian
4277 [ 'code' => 'he', 'locale' => 'he_IL', 'name' => 'Hebrew', 'native' => 'עברית' ],
4278 [ 'code' => 'fa', 'locale' => 'fa_IR', 'name' => 'Persian', 'native' => 'فارسی' ],
4279 [ 'code' => 'ur', 'locale' => 'ur', 'name' => 'Urdu', 'native' => 'اردو' ],
4280 [ 'code' => 'ku', 'locale' => 'ku', 'name' => 'Kurdish', 'native' => 'Kurdî' ],
4281 [ 'code' => 'az', 'locale' => 'az', 'name' => 'Azerbaijani', 'native' => 'Azərbaycan' ],
4282 [ 'code' => 'ka', 'locale' => 'ka_GE', 'name' => 'Georgian', 'native' => 'ქართული' ],
4283 [ 'code' => 'hy', 'locale' => 'hy', 'name' => 'Armenian', 'native' => 'Հայերեն' ],
4284 [ 'code' => 'kk', 'locale' => 'kk', 'name' => 'Kazakh', 'native' => 'Қазақша' ],
4285 [ 'code' => 'uz', 'locale' => 'uz_UZ', 'name' => 'Uzbek', 'native' => 'Oʻzbek' ],
4286 [ 'code' => 'tg', 'locale' => 'tg', 'name' => 'Tajik', 'native' => 'Тоҷикӣ' ],
4287 [ 'code' => 'ky', 'locale' => 'ky_KY', 'name' => 'Kyrgyz', 'native' => 'Кыргызча' ],
4288 [ 'code' => 'tk', 'locale' => 'tk', 'name' => 'Turkmen', 'native' => 'Türkmen' ],
4289 [ 'code' => 'mn', 'locale' => 'mn', 'name' => 'Mongolian', 'native' => 'Монгол' ],
4290 // South Asian
4291 [ 'code' => 'ta', 'locale' => 'ta_IN', 'name' => 'Tamil', 'native' => 'தமிழ்' ],
4292 [ 'code' => 'te', 'locale' => 'te', 'name' => 'Telugu', 'native' => 'తెలుగు' ],
4293 [ 'code' => 'mr', 'locale' => 'mr', 'name' => 'Marathi', 'native' => 'मराठी' ],
4294 [ 'code' => 'gu', 'locale' => 'gu', 'name' => 'Gujarati', 'native' => 'ગુજરાતી' ],
4295 [ 'code' => 'kn', 'locale' => 'kn', 'name' => 'Kannada', 'native' => 'ಕನ್ನಡ' ],
4296 [ 'code' => 'ml', 'locale' => 'ml_IN', 'name' => 'Malayalam', 'native' => 'മലയാളം' ],
4297 [ 'code' => 'pa', 'locale' => 'pa_IN', 'name' => 'Punjabi', 'native' => 'ਪੰਜਾਬੀ' ],
4298 [ 'code' => 'si', 'locale' => 'si_LK', 'name' => 'Sinhala', 'native' => 'සිංහල' ],
4299 [ 'code' => 'ne', 'locale' => 'ne_NP', 'name' => 'Nepali', 'native' => 'नेपाली' ],
4300 [ 'code' => 'my', 'locale' => 'my_MM', 'name' => 'Burmese', 'native' => 'မြန်မာ' ],
4301 [ 'code' => 'km', 'locale' => 'km', 'name' => 'Khmer', 'native' => 'ខ្មែរ' ],
4302 [ 'code' => 'lo', 'locale' => 'lo', 'name' => 'Lao', 'native' => 'ລາວ' ],
4303 // East & Southeast Asian
4304 [ 'code' => 'tl', 'locale' => 'tl', 'name' => 'Filipino', 'native' => 'Filipino' ],
4305 [ 'code' => 'jv', 'locale' => 'jv_ID', 'name' => 'Javanese', 'native' => 'Basa Jawa' ],
4306 [ 'code' => 'su', 'locale' => 'su_ID', 'name' => 'Sundanese', 'native' => 'Basa Sunda' ],
4307 // African languages
4308 [ 'code' => 'sw', 'locale' => 'sw', 'name' => 'Swahili', 'native' => 'Kiswahili' ],
4309 [ 'code' => 'am', 'locale' => 'am', 'name' => 'Amharic', 'native' => 'አማርኛ' ],
4310 [ 'code' => 'ha', 'locale' => 'ha', 'name' => 'Hausa', 'native' => 'Hausa' ],
4311 [ 'code' => 'yo', 'locale' => 'yo', 'name' => 'Yoruba', 'native' => 'Yorùbá' ],
4312 [ 'code' => 'ig', 'locale' => 'ig_NG', 'name' => 'Igbo', 'native' => 'Igbo' ],
4313 [ 'code' => 'zu', 'locale' => 'zu', 'name' => 'Zulu', 'native' => 'isiZulu' ],
4314 [ 'code' => 'xh', 'locale' => 'xh', 'name' => 'Xhosa', 'native' => 'isiXhosa' ],
4315 [ 'code' => 'af', 'locale' => 'af', 'name' => 'Afrikaans', 'native' => 'Afrikaans' ],
4316 [ 'code' => 'so', 'locale' => 'so_SO', 'name' => 'Somali', 'native' => 'Soomaali' ],
4317 [ 'code' => 'rw', 'locale' => 'rw_RW', 'name' => 'Kinyarwanda', 'native' => 'Kinyarwanda' ],
4318 [ 'code' => 'mg', 'locale' => 'mg_MG', 'name' => 'Malagasy', 'native' => 'Malagasy' ],
4319 // Other
4320 [ 'code' => 'eo', 'locale' => 'eo', 'name' => 'Esperanto', 'native' => 'Esperanto' ],
4321 [ 'code' => 'la', 'locale' => 'la', 'name' => 'Latin', 'native' => 'Latina' ],
4322 ];
4323 }
4324