jdarwood007 commited on 2023-03-13 20:34:47
Showing 5 changed files, with 320 additions and 5 deletions.
... | ... |
@@ -24,6 +24,7 @@ class SFSA |
24 | 24 |
*/ |
25 | 25 |
private $adminPageURL = null; |
26 | 26 |
private $adminLogURL = null; |
27 |
+ private $adminTestURL = null; |
|
27 | 28 |
|
28 | 29 |
/** |
29 | 30 |
* @var mixed Search area handling. |
... | ... |
@@ -109,7 +110,7 @@ class SFSA |
109 | 110 |
* |
110 | 111 |
* @internal |
111 | 112 |
* @CalledIn SMF 2.0, SMF 2.1 |
112 |
- * @version 1.0 |
|
113 |
+ * @version 1.4.0 |
|
113 | 114 |
* @since 1.0 |
114 | 115 |
* @uses integrate__admin_areas - Hook SMF2.0 |
115 | 116 |
* @uses integrate__admin_areas - Hook SMF2.1 |
... | ... |
@@ -124,6 +125,7 @@ class SFSA |
124 | 125 |
{ |
125 | 126 |
$this->adminPageURL = $scripturl . '?action=admin;area=modsettings;sa=sfs'; |
126 | 127 |
$this->adminLogURL = $scripturl . '?action=admin;area=modsettings;sa=sfslog'; |
128 |
+ $this->adminTestURL = $scripturl . '?action=admin;area=modsettings;sa=sfstest'; |
|
127 | 129 |
|
128 | 130 |
$admin_areas['config']['areas']['modsettings']['subsections']['sfs'] = array( |
129 | 131 |
$this->SFSclass->txt('sfs_admin_area') |
... | ... |
@@ -131,17 +133,21 @@ class SFSA |
131 | 133 |
$admin_areas['config']['areas']['modsettings']['subsections']['sfslog'] = array( |
132 | 134 |
$this->SFSclass->txt('sfs_admin_logs') |
133 | 135 |
); |
136 |
+ $admin_areas['config']['areas']['modsettings']['subsections']['sfstest'] = array( |
|
137 |
+ $this->SFSclass->txt('sfs_admin_test') |
|
138 |
+ ); |
|
134 | 139 |
} |
135 | 140 |
else |
136 | 141 |
{ |
137 | 142 |
$this->adminPageURL = $scripturl . '?action=admin;area=modsettings;sa=sfs'; |
138 | 143 |
$this->adminLogURL = $scripturl . '?action=admin;area=logs;sa=sfslog'; |
144 |
+ $this->adminTestURL = $scripturl . '?action=admin;area=regcenter;sa=sfstest'; |
|
139 | 145 |
|
140 | 146 |
$admin_areas['config']['areas']['modsettings']['subsections']['sfs'] = array( |
141 | 147 |
$this->SFSclass->txt('sfs_admin_area') |
142 | 148 |
); |
143 |
- $admin_areas['maintenance']['areas']['logs']['subsections']['sfslog'] = array( |
|
144 |
- $this->SFSclass->txt('sfs_admin_logs') |
|
149 |
+ $admin_areas['members']['areas']['regcenter']['subsections']['sfstest'] = array( |
|
150 |
+ $this->SFSclass->txt('sfs_admin_test') |
|
145 | 151 |
); |
146 | 152 |
} |
147 | 153 |
} |
... | ... |
@@ -174,7 +180,7 @@ class SFSA |
174 | 180 |
* |
175 | 181 |
* @internal |
176 | 182 |
* @CalledIn SMF 2.0, SMF 2.1 |
177 |
- * @version 1.0 |
|
183 |
+ * @version 1.4.0 |
|
178 | 184 |
* @since 1.0 |
179 | 185 |
* @uses integrate_modify_modifications - Hook SMF2.0 |
180 | 186 |
* @uses integrate_modify_modifications - Hook SMF2.1 |
... | ... |
@@ -186,7 +192,10 @@ class SFSA |
186 | 192 |
|
187 | 193 |
// Only in SMF 2.0 do we drop logs here. |
188 | 194 |
if ($this->SFSclass->versionCheck('2.0', 'smf')) |
195 |
+ { |
|
189 | 196 |
$subActions['sfslog'] = 'SFSA::startupLogs'; |
197 |
+ $subActions['sfstest'] = 'SFSA::startupTest'; |
|
198 |
+ } |
|
190 | 199 |
} |
191 | 200 |
|
192 | 201 |
/** |
... | ... |
@@ -215,7 +224,7 @@ class SFSA |
215 | 224 |
* |
216 | 225 |
* @internal |
217 | 226 |
* @CalledIn SMF 2.0, SMF 2.1 |
218 |
- * @version 1.0 |
|
227 |
+ * @version 1.4.0 |
|
219 | 228 |
* @since 1.0 |
220 | 229 |
* @uses integrate_modify_modifications - Hook SMF2.0 |
221 | 230 |
* @uses integrate_modify_modifications - Hook SMF2.1 |
... | ... |
@@ -1151,4 +1160,163 @@ class SFSA |
1151 | 1160 |
else |
1152 | 1161 |
return $this->search_params['type']; |
1153 | 1162 |
} |
1163 |
+ |
|
1164 |
+ /** |
|
1165 |
+ * In some software/versions, we can hook into the members registration center section. |
|
1166 |
+ * In others we hook into the modifications settings. |
|
1167 |
+ * |
|
1168 |
+ * @param array $subActions All possible sub actions. |
|
1169 |
+ * |
|
1170 |
+ * @api |
|
1171 |
+ * @CalledIn SMF 2.1 |
|
1172 |
+ * @See SFSA::startupTest |
|
1173 |
+ * @version 1.4.0 |
|
1174 |
+ * @since 1.4.0 |
|
1175 |
+ * @uses integrate_manage_registrations - Hook SMF2.1 |
|
1176 |
+ * @return void No return is generated |
|
1177 |
+ */ |
|
1178 |
+ public static function hook_manage_registrations(array &$subActions): bool |
|
1179 |
+ { |
|
1180 |
+ global $context; |
|
1181 |
+ |
|
1182 |
+ // Add our logs sub action. |
|
1183 |
+ $subActions['sfstest'] = array('SFSA::startupTest', 'admin_forum'); |
|
1184 |
+ |
|
1185 |
+ if (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'sfstest' && allowedTo('admin_forum')) |
|
1186 |
+ $context['sub_action'] = 'sfstest'; |
|
1187 |
+ |
|
1188 |
+ return self::selfClass()->AddToRegCenterMenu($subActions); |
|
1189 |
+ } |
|
1190 |
+ |
|
1191 |
+ /** |
|
1192 |
+ * Add the SFS Test to the regcenter menu. |
|
1193 |
+ * |
|
1194 |
+ * @param array $log_functions All possible log functions. |
|
1195 |
+ * |
|
1196 |
+ * @CalledIn SMF 2.1 |
|
1197 |
+ * @See SFSA::startupTest |
|
1198 |
+ * @version 1.4.0 |
|
1199 |
+ * @since 1.4.0 |
|
1200 |
+ * @return void No return is generated |
|
1201 |
+ */ |
|
1202 |
+ public function AddToRegCenterMenu(array &$subActions): bool |
|
1203 |
+ { |
|
1204 |
+ global $context; |
|
1205 |
+ |
|
1206 |
+ $context[$context['admin_menu_name']]['tab_data']['tabs']['sfstest'] = array( |
|
1207 |
+ 'description' => $this->SFSclass->txt('sfs_admin_test_desc'), |
|
1208 |
+ ); |
|
1209 |
+ |
|
1210 |
+ return true; |
|
1211 |
+ } |
|
1212 |
+ |
|
1213 |
+ /** |
|
1214 |
+ * Test API startup caller. |
|
1215 |
+ * This has a $return_config just for simply complying with properly for searching the admin panel. |
|
1216 |
+ * |
|
1217 |
+ * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs. |
|
1218 |
+ * |
|
1219 |
+ * @api |
|
1220 |
+ * @CalledIn SMF 2.1 |
|
1221 |
+ * @See SFSA::loadTestAPI |
|
1222 |
+ * @version 1.4.0 |
|
1223 |
+ * @since 1.4.0 |
|
1224 |
+ * @uses hook_manage_registrations - Hook SMF2.1 |
|
1225 |
+ * @uses setupModifyModifications - Injected SMF2.0 |
|
1226 |
+ * @return void No return is generated |
|
1227 |
+ */ |
|
1228 |
+ public static function startupTest(bool $return_config = false): array |
|
1229 |
+ { |
|
1230 |
+ return self::selfClass()->loadTestAPI(); |
|
1231 |
+ } |
|
1232 |
+ |
|
1233 |
+ /** |
|
1234 |
+ * Actually do the test API. |
|
1235 |
+ * This has a $return_config just for simply complying with properly for searching the admin panel. |
|
1236 |
+ * |
|
1237 |
+ * @param bool $return_config If true, returns empty array to prevent breaking old SMF installs. |
|
1238 |
+ * |
|
1239 |
+ * @api |
|
1240 |
+ * @CalledIn SMF2.0, SMF 2.1 |
|
1241 |
+ * @version 1.4.0 |
|
1242 |
+ * @since 1.4.0 |
|
1243 |
+ * @uses hook_manage_registrations - Hook SMF2.1 |
|
1244 |
+ * @uses setupModifyModifications - Injected SMF2.0 |
|
1245 |
+ * @return void No return is generated |
|
1246 |
+ */ |
|
1247 |
+ public function loadTestAPI(bool $return_config = false): array |
|
1248 |
+ { |
|
1249 |
+ global $context, $smcFunc, $user_info; |
|
1250 |
+ |
|
1251 |
+ // No Configs. |
|
1252 |
+ if ($return_config) |
|
1253 |
+ return array(); |
|
1254 |
+ |
|
1255 |
+ $context['token_check'] = 'sfs_testapi'; |
|
1256 |
+ $this->SFSclass->loadLanguage(); |
|
1257 |
+ |
|
1258 |
+ // The reuslts output. |
|
1259 |
+ $context['test_sent'] = isset($_POST['send']); |
|
1260 |
+ $context['sfs_checks'] = array( |
|
1261 |
+ 'username' => array( |
|
1262 |
+ 0 => array( |
|
1263 |
+ 'enabled' => !empty($modSettings['sfs_usernamecheck']), |
|
1264 |
+ 'value' => !empty($_POST['username']) ? $smcFunc['htmlspecialchars']($_POST['username']) : $user_info['name'], |
|
1265 |
+ 'results' => null |
|
1266 |
+ ), |
|
1267 |
+ ), |
|
1268 |
+ 'email' => array( |
|
1269 |
+ 0 => array( |
|
1270 |
+ 'enabled' => !empty($modSettings['sfs_emailcheck']), |
|
1271 |
+ 'value' => !empty($_POST['email']) ? $smcFunc['htmlspecialchars']($_POST['email']) : $user_info['email'], |
|
1272 |
+ 'results' => null |
|
1273 |
+ ), |
|
1274 |
+ ), |
|
1275 |
+ 'ip' => array( |
|
1276 |
+ 0 => array( |
|
1277 |
+ 'enabled' => !empty($modSettings['sfs_ipcheck']), |
|
1278 |
+ 'value' => !empty($_POST['ip']) ? $smcFunc['htmlspecialchars']($_POST['ip']) : $user_info['ip'], |
|
1279 |
+ 'results' => null |
|
1280 |
+ ), |
|
1281 |
+ ), |
|
1282 |
+ ); |
|
1283 |
+ |
|
1284 |
+ // Sending the data? |
|
1285 |
+ if ($context['test_sent']) |
|
1286 |
+ { |
|
1287 |
+ //checkSession(); |
|
1288 |
+ //if (!$this->SFSclass->versionCheck('2.0', 'smf')) |
|
1289 |
+ // validateToken($context['token_check'], 'post'); |
|
1290 |
+ |
|
1291 |
+ $username = $smcFunc['htmlspecialchars']($_POST['username']); |
|
1292 |
+ $email = $smcFunc['htmlspecialchars']($_POST['email']); |
|
1293 |
+ $ip = $smcFunc['htmlspecialchars']($_POST['ip']); |
|
1294 |
+ |
|
1295 |
+ $response = $this->SFSclass->TestSFS(array( |
|
1296 |
+ array('username' => $username), |
|
1297 |
+ array('email' => $email), |
|
1298 |
+ array('ip' => $ip), |
|
1299 |
+ )); |
|
1300 |
+ |
|
1301 |
+ // No checks found? Can't do this. |
|
1302 |
+ if (empty($response) || !is_array($response) || empty($response['success'])) |
|
1303 |
+ $context['test_api_error'] = $this->SFSclass->txt('sfs_request_failure_nodata'); |
|
1304 |
+ else |
|
1305 |
+ // Parse all the responses out. |
|
1306 |
+ foreach($context['sfs_checks'] as $key => &$res) |
|
1307 |
+ $res[0] += $response[$key][0]; |
|
1308 |
+ } |
|
1309 |
+ |
|
1310 |
+ // Load our template. |
|
1311 |
+ loadTemplate('StopForumSpam'); |
|
1312 |
+ $context['sub_template'] = 'sfsa_testapi'; |
|
1313 |
+ |
|
1314 |
+ $context['sfs_test_url'] = $this->adminTestURL; |
|
1315 |
+ if (!$this->SFSclass->versionCheck('2.0', 'smf')) |
|
1316 |
+ createToken($context['token_check'], 'post'); |
|
1317 |
+ else |
|
1318 |
+ unset($context['token_check']); |
|
1319 |
+ |
|
1320 |
+ return array(); |
|
1321 |
+ } |
|
1154 | 1322 |
} |
1155 | 1323 |
\ No newline at end of file |
... | ... |
@@ -1459,6 +1459,32 @@ class SFS |
1459 | 1459 |
return true; |
1460 | 1460 |
} |
1461 | 1461 |
|
1462 |
+ /** |
|
1463 |
+ * The caller for a test check. |
|
1464 |
+ * |
|
1465 |
+ * @param array $checks The data we are checking. |
|
1466 |
+ * |
|
1467 |
+ * @api |
|
1468 |
+ * @CalledIn SMF 2.0, SMF 2.1 |
|
1469 |
+ * @version 1.4.0 |
|
1470 |
+ * @since 1.4.0 |
|
1471 |
+ * @return array The results of the check. |
|
1472 |
+ */ |
|
1473 |
+ public function TestSFS(array $checks): array |
|
1474 |
+ { |
|
1475 |
+ $requestURL = $this->buildServerURL(); |
|
1476 |
+ |
|
1477 |
+ // Lets build our data set, always send it as a bulk. |
|
1478 |
+ $singleCheckFound = $this->buildCheckPath($requestURL, $checks, 'test'); |
|
1479 |
+ |
|
1480 |
+ // No checks found? Can't do this. |
|
1481 |
+ if (empty($singleCheckFound)) |
|
1482 |
+ return []; |
|
1483 |
+ |
|
1484 |
+ // Send this off. |
|
1485 |
+ return $this->sendSFSCheck($requestURL, $checks, 'test'); |
|
1486 |
+ } |
|
1487 |
+ |
|
1462 | 1488 |
/** |
1463 | 1489 |
* Get some data |
1464 | 1490 |
* |
... | ... |
@@ -109,3 +109,113 @@ function template_profile_tracksfs() |
109 | 109 |
</form>'; |
110 | 110 |
} |
111 | 111 |
} |
112 |
+ |
|
113 |
+function template_sfsa_testapi() |
|
114 |
+{ |
|
115 |
+ global $context, $txt, $scripturl; |
|
116 |
+ |
|
117 |
+ echo ' |
|
118 |
+ <div id="admin_form_wrapper"> |
|
119 |
+ <form id="postForm" action="', $context['sfs_test_url'], '" method="post" accept-charset="', $context['character_set'], '" name="postForm"> |
|
120 |
+ <div class="cat_bar"> |
|
121 |
+ <h3 class="catbg">', $txt['sfs_testapi_title'], '</h3> |
|
122 |
+ </div> |
|
123 |
+ <dl class="register_form" id="sfs_testapi_form"> |
|
124 |
+ <dt> |
|
125 |
+ <strong><label for="username_input">', $txt['username'], ':</label></strong> |
|
126 |
+ </dt> |
|
127 |
+ <dd> |
|
128 |
+ <input type="text" name="username" id="username_input" tabindex="', $context['tabindex']++, '" size="50" maxlength="25" value="', $context['sfs_checks']['username'][0]['value'], '"> |
|
129 |
+ </dd> |
|
130 |
+ <dt> |
|
131 |
+ <strong><label for="email_input">', $txt['email_address'], ':</label></strong> |
|
132 |
+ </dt> |
|
133 |
+ <dd> |
|
134 |
+ <input type="email" name="email" id="email_input" tabindex="', $context['tabindex']++, '" size="50" value="', $context['sfs_checks']['email'][0]['value'], '"> |
|
135 |
+ </dd> |
|
136 |
+ <dt> |
|
137 |
+ <strong><label for="ip_input">', $txt['ip_address'], ':</label></strong> |
|
138 |
+ </dt> |
|
139 |
+ <dd> |
|
140 |
+ <input type="text" name="ip" id="ip_input" tabindex="', $context['tabindex']++, '" size="100" value="', $context['sfs_checks']['ip'][0]['value'], '"> |
|
141 |
+ </dd> |
|
142 |
+ </dl> |
|
143 |
+ <div class="flow_auto"> |
|
144 |
+ <input type="submit" name="send" value="', $txt['sfs_testapi_submit'], '" tabindex="', $context['tabindex']++, '" class="button"> |
|
145 |
+ <input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '">'; |
|
146 |
+ |
|
147 |
+ if (!empty($context['token_check'])) |
|
148 |
+ echo ' |
|
149 |
+ <input type="hidden" name="' . $context[$context['token_check'] . '_token_var'] . '" value="' . $context[$context['token_check'] . '_token'] . '">'; |
|
150 |
+ |
|
151 |
+ echo ' |
|
152 |
+ </div> |
|
153 |
+ </div><!-- #sfs_testapi_form --> |
|
154 |
+ </form> |
|
155 |
+ </div><!-- #admin_form_wrapper --> |
|
156 |
+ <br class="clear">'; |
|
157 |
+ |
|
158 |
+ // Do not show results yet. |
|
159 |
+ if (empty($context['test_sent'])) |
|
160 |
+ return; |
|
161 |
+ |
|
162 |
+ echo ' |
|
163 |
+ <div class="tborder"> |
|
164 |
+ <div class="cat_bar"> |
|
165 |
+ <h3 class="catbg">', $txt['sfs_testapi_results'], '</h3> |
|
166 |
+ </div> |
|
167 |
+ |
|
168 |
+ <table class="table_grid"> |
|
169 |
+ <thead> |
|
170 |
+ <tr class="title_bar"> |
|
171 |
+ <th class="lefttext">', $txt['sfs_check'], '</th> |
|
172 |
+ <th class="lefttext">', $txt['sfs_value'], '</th> |
|
173 |
+ <th class="lefttext">', $txt['sfs_result'], '</th> |
|
174 |
+ </tr> |
|
175 |
+ </thead> |
|
176 |
+ <tbody>'; |
|
177 |
+ |
|
178 |
+ foreach ($context['sfs_checks'] as $id_check => $checkGrp) |
|
179 |
+ { |
|
180 |
+ foreach ($checkGrp as $check) |
|
181 |
+ { |
|
182 |
+ echo ' |
|
183 |
+ <tr class="windowbg"> |
|
184 |
+ <td title="sfs_check_', $id_check, '"> |
|
185 |
+ ', $txt['sfs_check_' . $id_check], ' |
|
186 |
+ </td> |
|
187 |
+ <td> |
|
188 |
+ ', $check['value'], ' |
|
189 |
+ </td> |
|
190 |
+ <td class="smalltext"> |
|
191 |
+ ', (!empty($check['appears']) ? $txt['yes'] : $txt['no']); |
|
192 |
+ |
|
193 |
+ // Some checks will show the last seen, convert it and show it. |
|
194 |
+ if (!empty($check['lastseen'])) |
|
195 |
+ echo '<br>' . $txt['sfs_last_seen'] . ': ' . timeformat(strtotime($check['lastseen'])); |
|
196 |
+ |
|
197 |
+ if (!empty($check['confidence'])) |
|
198 |
+ echo '<br>' . $txt['sfs_confidence'] . ': ' . $check['confidence']; |
|
199 |
+ |
|
200 |
+ if (!empty($check['frequency'])) |
|
201 |
+ echo '<br>' . $txt['sfs_frequency'] . ': ' . $check['frequency']; |
|
202 |
+ |
|
203 |
+ // IP address may be normalized |
|
204 |
+ if (!empty($check['torexit'])) |
|
205 |
+ echo '<br>' . $txt['sfs_torexit']; |
|
206 |
+ |
|
207 |
+ // IP address may be normalized |
|
208 |
+ if (!empty($check['asn'])) |
|
209 |
+ echo '<br><a href="', $scripturl, '?action=profile;area=tracking;sa=ip;searchip=' . urlencode(str_replace('::*', ':*', !empty($check['normalized']) ? $check['normalized'] . '*' : $check['value'])) . '">', $txt['ip_address'], ' ', (!empty($check['normalized']) ? $check['normalized'] . '*' : $check['value']), '</a>'; |
|
210 |
+ |
|
211 |
+ echo ' |
|
212 |
+ </td> |
|
213 |
+ </tr>'; |
|
214 |
+ } |
|
215 |
+ } |
|
216 |
+ |
|
217 |
+ echo ' |
|
218 |
+ </tbody> |
|
219 |
+ </table>'; |
|
220 |
+ |
|
221 |
+} |
|
112 | 222 |
\ No newline at end of file |
... | ... |
@@ -3,6 +3,8 @@ |
3 | 3 |
/* The section Name */ |
4 | 4 |
$txt['sfs_admin_area'] = 'Stop Forum Spam'; |
5 | 5 |
$txt['sfs_admin_logs'] = 'SFS Logs'; |
6 |
+$txt['sfs_admin_test'] = 'Test SFS'; |
|
7 |
+$txt['sfs_admin_test_desc'] = 'Test Stop Forum Spam API and your configuration'; |
|
6 | 8 |
|
7 | 9 |
/* Admin Section General header */ |
8 | 10 |
$txt['sfs_general_title'] = 'General Configuration'; |
... | ... |
@@ -60,6 +62,13 @@ $txt['sfs_verOptionsMemExtra'] = 'Member Verification Sections'; |
60 | 62 |
$txt['sfs_verfOptMemPostThreshold'] = 'Post Count after which we stop these checks.'; |
61 | 63 |
$txt['sfs_verification_options_membersextra'] = 'Extra sections'; |
62 | 64 |
|
65 |
+/* Admin section: Test API */ |
|
66 |
+$txt['sfs_testapi_error'] = 'The API failed to communicate with the SFS servers'; |
|
67 |
+$txt['sfs_testapi_title'] = 'Enter test information'; |
|
68 |
+$txt['sfs_testapi_results'] = 'Results of Testing API'; |
|
69 |
+$txt['sfs_value'] = 'Value'; |
|
70 |
+$txt['sfs_testapi_submit'] = 'Send API Test'; |
|
71 |
+ |
|
63 | 72 |
/* Request handling */ |
64 | 73 |
$txt['sfs_request_failure'] = 'SFS Failed with invalid response'; |
65 | 74 |
$txt['sfs_request_failure_nodata'] = 'SFS Failed as no data was sent'; |
... | ... |
@@ -76,6 +76,7 @@ |
76 | 76 |
<hook hook="integrate_admin_areas" function="SFSA::hook_admin_areas" /> |
77 | 77 |
<hook hook="integrate_modify_modifications" function="SFSA::hook_modify_modifications" /> |
78 | 78 |
<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" /> |
79 |
+ <hook hook="integrate_manage_registrations" function="SFSA::hook_manage_registrations" /> |
|
79 | 80 |
|
80 | 81 |
<!-- Profile Section --> |
81 | 82 |
<hook hook="integrate_pre_profile_areas" function="SFS::hook_pre_profile_areas" /> |
... | ... |
@@ -99,6 +100,7 @@ |
99 | 100 |
<hook hook="integrate_admin_areas" function="SFSA::hook_admin_areas" reverse="true" /> |
100 | 101 |
<hook hook="integrate_modify_modifications" function="SFSA::hook_modify_modifications" reverse="true" /> |
101 | 102 |
<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" reverse="true" /> |
103 |
+ <hook hook="integrate_manage_registrations" function="SFSA::hook_manage_registrations" reverse="true" /> |
|
102 | 104 |
|
103 | 105 |
<!-- Profile Section --> |
104 | 106 |
<hook hook="integrate_pre_profile_areas" function="SFS::hook_pre_profile_areas" reverse="true" /> |
105 | 107 |