PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/icwp-optionshandler-base.php

https://github.com/stackgrinder/wp-simple-firewall
PHP | 652 lines | 386 code | 86 blank | 180 comment | 101 complexity | 13d6262c565237c48219cfd410fdc276 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright (c) 2014 iControlWP <support@icontrolwp.com>
  4. * All rights reserved.
  5. *
  6. * Version: 2013-11-15-V1
  7. *
  8. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  9. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  10. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  11. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  12. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  13. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  14. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  15. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  16. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  17. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  18. */
  19. if ( !class_exists('ICWP_OptionsHandler_Base_V2') ):
  20. class ICWP_OptionsHandler_Base_V2 {
  21. /**
  22. * @var ICWP_Wordpress_Simple_Firewall_Plugin
  23. */
  24. protected $oPluginVo;
  25. /**
  26. * @var string
  27. */
  28. const CollateSeparator = '--SEP--';
  29. /**
  30. * @var string
  31. */
  32. const PluginVersionKey = 'current_plugin_version';
  33. /**
  34. * @var boolean
  35. */
  36. protected $fNeedSave;
  37. /**
  38. * @var array
  39. */
  40. protected $m_aOptions;
  41. /**
  42. * These are options that need to be stored, but are never set by the UI.
  43. *
  44. * @var array
  45. */
  46. protected $m_aNonUiOptions;
  47. /**
  48. * @var array
  49. */
  50. protected $m_aOptionsValues;
  51. /**
  52. * @var string
  53. */
  54. protected $sOptionsStoreKey;
  55. /**
  56. * @var array
  57. */
  58. protected $aOptionsKeys;
  59. /**
  60. * @var string
  61. */
  62. protected $sFeatureName;
  63. /**
  64. * @var string
  65. */
  66. protected $sFeatureSlug;
  67. /**
  68. * @var string
  69. */
  70. protected $fShowFeatureMenuItem = true;
  71. public function __construct( $oPluginVo, $sOptionsStoreKey ) {
  72. $this->oPluginVo = $oPluginVo;
  73. $this->sOptionsStoreKey = $this->prefixOptionKey( $sOptionsStoreKey );
  74. // Handle any upgrades as necessary (only go near this if it's the admin area)
  75. add_action( 'init', array( $this, 'onWpInit' ), 1 );
  76. add_action( $this->doPrefix( 'form_submit', '_' ), array( $this, 'updatePluginOptionsFromSubmit' ) );
  77. add_filter( $this->doPrefix( 'filter_plugin_submenu_items', '_' ), array( $this, 'filter_addPluginSubMenuItem' ) );
  78. }
  79. /**
  80. * @param $aItems
  81. * @return mixed
  82. */
  83. public function filter_addPluginSubMenuItem( $aItems ) {
  84. if ( !$this->fShowFeatureMenuItem || empty($this->sFeatureName) ) {
  85. return $aItems;
  86. }
  87. $sMenuPageTitle = $this->oPluginVo->getHumanName().' - '.$this->sFeatureName;
  88. $aItems[ $sMenuPageTitle ] = array(
  89. $this->sFeatureName,
  90. $this->doPrefix( $this->sFeatureSlug ),
  91. 'onDisplayAll'
  92. );
  93. return $aItems;
  94. }
  95. /**
  96. * A action added to WordPress 'plugins_loaded' hook
  97. */
  98. public function onWpInit() {
  99. $this->doUpdates();
  100. }
  101. protected function doUpdates() {
  102. if ( $this->hasPluginManageRights() ) {
  103. $this->buildOptions();
  104. $this->updateHandler();
  105. }
  106. }
  107. /**
  108. * @return bool
  109. */
  110. public function hasPluginManageRights() {
  111. if ( !current_user_can( $this->oPluginVo->getBasePermissions() ) ) {
  112. return false;
  113. }
  114. $oWpFunc = $this->loadWpFunctions();
  115. if ( is_admin() && !$oWpFunc->isMultisite() ) {
  116. return true;
  117. }
  118. else if ( is_network_admin() && $oWpFunc->isMultisite() ) {
  119. return true;
  120. }
  121. return false;
  122. }
  123. /**
  124. * @return string
  125. */
  126. public function getVersion() {
  127. $sVersion = $this->getOpt( self::PluginVersionKey );
  128. return empty( $sVersion )? '0.0' : $sVersion;
  129. }
  130. /**
  131. * Gets the array of all possible options keys
  132. *
  133. * @return array
  134. */
  135. public function getOptionsKeys() {
  136. $this->setOptionsKeys();
  137. return $this->aOptionsKeys;
  138. }
  139. /**
  140. * @return void
  141. */
  142. public function setOptionsKeys() {
  143. if ( !empty( $this->aOptionsKeys ) ) {
  144. return;
  145. }
  146. $this->buildOptions();
  147. }
  148. /**
  149. * Determines whether the given option key is a valid option
  150. *
  151. * @param string
  152. * @return boolean
  153. */
  154. public function getIsOptionKey( $sOptionKey ) {
  155. if ( $sOptionKey == self::PluginVersionKey ) {
  156. return true;
  157. }
  158. $this->setOptionsKeys();
  159. return ( in_array( $sOptionKey, $this->aOptionsKeys ) );
  160. }
  161. /**
  162. * Sets the value for the given option key
  163. *
  164. * @param string $insKey
  165. * @param mixed $inmValue
  166. * @return boolean
  167. */
  168. public function setOpt( $insKey, $inmValue ) {
  169. if ( !$this->getIsOptionKey( $insKey ) ) {
  170. return false;
  171. }
  172. if ( !isset( $this->m_aOptionsValues ) ) {
  173. $this->loadStoredOptionsValues();
  174. }
  175. if ( $this->getOpt( $insKey ) === $inmValue ) {
  176. return true;
  177. }
  178. $this->m_aOptionsValues[ $insKey ] = $inmValue;
  179. if ( !$this->fNeedSave ) {
  180. $this->fNeedSave = true;
  181. }
  182. return true;
  183. }
  184. /**
  185. * @param string $insKey
  186. * @return Ambigous <boolean, multitype:>
  187. */
  188. public function getOpt( $insKey ) {
  189. if ( !isset( $this->m_aOptionsValues ) ) {
  190. $this->loadStoredOptionsValues();
  191. }
  192. return ( isset( $this->m_aOptionsValues[ $insKey ] )? $this->m_aOptionsValues[ $insKey ] : false );
  193. }
  194. /**
  195. * Retrieves the full array of options->values
  196. *
  197. * @return array
  198. */
  199. public function getOptions() {
  200. $this->buildOptions();
  201. return $this->m_aOptions;
  202. }
  203. /**
  204. * Loads the options and their stored values from the WordPress Options store.
  205. *
  206. * @return array
  207. */
  208. public function getPluginOptionsValues() {
  209. $this->generateOptionsValues();
  210. return $this->m_aOptionsValues;
  211. }
  212. /**
  213. * Saves the options to the WordPress Options store.
  214. *
  215. * It will also update the stored plugin options version.
  216. */
  217. public function savePluginOptions() {
  218. $this->doPrePluginOptionsSave();
  219. $this->updateOptionsVersion();
  220. if ( !$this->fNeedSave ) {
  221. return true;
  222. }
  223. $oWpFunc = $this->loadWpFunctions();
  224. $oWpFunc->updateOption( $this->sOptionsStoreKey, $this->m_aOptionsValues );
  225. $this->fNeedSave = false;
  226. }
  227. public function collateAllFormInputsForAllOptions() {
  228. if ( !isset( $this->m_aOptions ) ) {
  229. $this->buildOptions();
  230. }
  231. $aToJoin = array();
  232. foreach ( $this->m_aOptions as $aOptionsSection ) {
  233. if ( empty( $aOptionsSection ) ) {
  234. continue;
  235. }
  236. foreach ( $aOptionsSection['section_options'] as $aOption ) {
  237. list($sKey, $fill1, $fill2, $sType) = $aOption;
  238. $aToJoin[] = (is_array($sType) ? array_shift($sType): $sType).':'.$sKey;
  239. }
  240. }
  241. return implode( self::CollateSeparator, $aToJoin );
  242. }
  243. /**
  244. * @return array
  245. */
  246. protected function generateOptionsValues() {
  247. if ( !isset( $this->m_aOptionsValues ) ) {
  248. $this->loadStoredOptionsValues();
  249. }
  250. if ( empty( $this->m_aOptionsValues ) ) {
  251. $this->buildOptions(); // set the defaults
  252. }
  253. }
  254. /**
  255. * Loads the options and their stored values from the WordPress Options store.
  256. */
  257. protected function loadStoredOptionsValues() {
  258. if ( empty( $this->m_aOptionsValues ) ) {
  259. $oWpFunc = $this->loadWpFunctions();
  260. $this->m_aOptionsValues = $oWpFunc->getOption( $this->sOptionsStoreKey, array() );
  261. if ( empty( $this->m_aOptionsValues ) ) {
  262. $this->fNeedSave = true;
  263. }
  264. }
  265. }
  266. protected function defineOptions() {
  267. if ( !empty( $this->m_aOptions ) ) {
  268. return true;
  269. }
  270. $aMisc = array(
  271. 'section_title' => 'Miscellaneous Plugin Options',
  272. 'section_options' => array(
  273. array(
  274. 'delete_on_deactivate',
  275. '',
  276. 'N',
  277. 'checkbox',
  278. 'Delete Plugin Settings',
  279. 'Delete All Plugin Settings Upon Plugin Deactivation',
  280. 'Careful: Removes all plugin options when you deactivite the plugin.'
  281. ),
  282. ),
  283. );
  284. $this->m_aOptions = array( $aMisc );
  285. }
  286. /**
  287. * Will initiate the plugin options structure for use by the UI builder.
  288. *
  289. * It will also fill in $this->m_aOptionsValues with defaults where appropriate.
  290. *
  291. * It doesn't set any values, just populates the array created in buildOptions()
  292. * with values stored.
  293. *
  294. * It has to handle the conversion of stored values to data to be displayed to the user.
  295. *
  296. * @param string $insUpdateKey - if only want to update a single key, supply it here.
  297. */
  298. public function buildOptions() {
  299. $this->defineOptions();
  300. $this->loadStoredOptionsValues();
  301. $this->aOptionsKeys = array();
  302. foreach ( $this->m_aOptions as &$aOptionsSection ) {
  303. if ( empty( $aOptionsSection ) || !isset( $aOptionsSection['section_options'] ) ) {
  304. continue;
  305. }
  306. foreach ( $aOptionsSection['section_options'] as &$aOptionParams ) {
  307. list( $sOptionKey, $sOptionValue, $sOptionDefault, $sOptionType ) = $aOptionParams;
  308. $this->aOptionsKeys[] = $sOptionKey;
  309. if ( $this->getOpt( $sOptionKey ) === false ) {
  310. $this->setOpt( $sOptionKey, $sOptionDefault );
  311. }
  312. $mCurrentOptionVal = $this->getOpt( $sOptionKey );
  313. if ( $sOptionType == 'password' && !empty( $mCurrentOptionVal ) ) {
  314. $mCurrentOptionVal = '';
  315. }
  316. else if ( $sOptionType == 'ip_addresses' ) {
  317. if ( empty( $mCurrentOptionVal ) ) {
  318. $mCurrentOptionVal = '';
  319. }
  320. else {
  321. $mCurrentOptionVal = implode( "\n", $this->convertIpListForDisplay( $mCurrentOptionVal ) );
  322. }
  323. }
  324. else if ( $sOptionType == 'yubikey_unique_keys' ) {
  325. if ( empty( $mCurrentOptionVal ) ) {
  326. $mCurrentOptionVal = '';
  327. }
  328. else {
  329. $aDisplay = array();
  330. foreach( $mCurrentOptionVal as $aParts ) {
  331. $aDisplay[] = key($aParts) .', '. reset($aParts);
  332. }
  333. $mCurrentOptionVal = implode( "\n", $aDisplay );
  334. }
  335. }
  336. else if ( $sOptionType == 'comma_separated_lists' ) {
  337. if ( empty( $mCurrentOptionVal ) ) {
  338. $mCurrentOptionVal = '';
  339. }
  340. else {
  341. $aNewValues = array();
  342. foreach( $mCurrentOptionVal as $sPage => $aParams ) {
  343. $aNewValues[] = $sPage.', '. implode( ", ", $aParams );
  344. }
  345. $mCurrentOptionVal = implode( "\n", $aNewValues );
  346. }
  347. }
  348. $aOptionParams[1] = $mCurrentOptionVal;
  349. }
  350. }
  351. // Cater for Non-UI options that don't necessarily go through the UI
  352. if ( isset($this->m_aNonUiOptions) && is_array($this->m_aNonUiOptions) ) {
  353. foreach( $this->m_aNonUiOptions as $sOption ) {
  354. $this->aOptionsKeys[] = $sOption;
  355. if ( !$this->getOpt( $sOption ) ) {
  356. $this->setOpt( $sOption, '' );
  357. }
  358. }
  359. }
  360. }
  361. /**
  362. * This is the point where you would want to do any options verification
  363. */
  364. protected function doPrePluginOptionsSave() { }
  365. /**
  366. */
  367. protected function updateOptionsVersion() {
  368. $this->setOpt( self::PluginVersionKey, $this->oPluginVo->getVersion() );
  369. }
  370. /**
  371. * Deletes all the options including direct save.
  372. */
  373. public function deletePluginOptions() {
  374. $oWpFunc = $this->loadWpFunctions();
  375. $oWpFunc->deleteOption( $this->sOptionsStoreKey );
  376. }
  377. protected function convertIpListForDisplay( $inaIpList = array() ) {
  378. $aDisplay = array();
  379. if ( empty( $inaIpList ) || empty( $inaIpList['ips'] ) ) {
  380. return $aDisplay;
  381. }
  382. foreach( $inaIpList['ips'] as $sAddress ) {
  383. // offset=1 in the case that it's a range and the first number is negative on 32-bit systems
  384. $mPos = strpos( $sAddress, '-', 1 );
  385. if ( $mPos === false ) { //plain IP address
  386. $sDisplayText = long2ip( $sAddress );
  387. }
  388. else {
  389. //we remove the first character in case this is '-'
  390. $aParts = array( substr( $sAddress, 0, 1 ), substr( $sAddress, 1 ) );
  391. list( $nStart, $nEnd ) = explode( '-', $aParts[1], 2 );
  392. $sDisplayText = long2ip( $aParts[0].$nStart ) .'-'. long2ip( $nEnd );
  393. }
  394. $sLabel = $inaIpList['meta'][ md5($sAddress) ];
  395. $sLabel = trim( $sLabel, '()' );
  396. $aDisplay[] = $sDisplayText . ' ('.$sLabel.')';
  397. }
  398. return $aDisplay;
  399. }
  400. /**
  401. * @param string $sAllOptionsInput - comma separated list of all the input keys to be processed from the $_POST
  402. * @return void|boolean
  403. */
  404. public function updatePluginOptionsFromSubmit( $sAllOptionsInput ) {
  405. if ( empty( $sAllOptionsInput ) ) {
  406. return;
  407. }
  408. $this->loadDataProcessor();
  409. $this->loadStoredOptionsValues();
  410. $aAllInputOptions = explode( self::CollateSeparator, $sAllOptionsInput );
  411. foreach ( $aAllInputOptions as $sInputKey ) {
  412. $aInput = explode( ':', $sInputKey );
  413. list( $sOptionType, $sOptionKey ) = $aInput;
  414. if ( !$this->getIsOptionKey( $sOptionKey ) ) {
  415. continue;
  416. }
  417. $sOptionValue = ICWP_WPSF_DataProcessor::FetchPost( $this->prefixOptionKey( $sOptionKey ) );
  418. if ( is_null($sOptionValue) ) {
  419. if ( $sOptionType == 'text' || $sOptionType == 'email' ) { //if it was a text box, and it's null, don't update anything
  420. continue;
  421. }
  422. else if ( $sOptionType == 'checkbox' ) { //if it was a checkbox, and it's null, it means 'N'
  423. $sOptionValue = 'N';
  424. }
  425. else if ( $sOptionType == 'integer' ) { //if it was a integer, and it's null, it means '0'
  426. $sOptionValue = 0;
  427. }
  428. }
  429. else { //handle any pre-processing we need to.
  430. if ( $sOptionType == 'integer' ) {
  431. $sOptionValue = intval( $sOptionValue );
  432. }
  433. else if ( $sOptionType == 'password' && $this->hasEncryptOption() ) { //md5 any password fields
  434. $sTempValue = trim( $sOptionValue );
  435. if ( empty( $sTempValue ) ) {
  436. continue;
  437. }
  438. $sOptionValue = md5( $sTempValue );
  439. }
  440. else if ( $sOptionType == 'ip_addresses' ) { //ip addresses are textareas, where each is separated by newline
  441. $sOptionValue = ICWP_WPSF_DataProcessor::ExtractIpAddresses( $sOptionValue );
  442. }
  443. else if ( $sOptionType == 'yubikey_unique_keys' ) { //ip addresses are textareas, where each is separated by newline and are 12 chars long
  444. $sOptionValue = ICWP_WPSF_DataProcessor::CleanYubikeyUniqueKeys( $sOptionValue );
  445. }
  446. else if ( $sOptionType == 'email' && function_exists( 'is_email' ) && !is_email( $sOptionValue ) ) {
  447. $sOptionValue = '';
  448. }
  449. else if ( $sOptionType == 'comma_separated_lists' ) {
  450. $sOptionValue = ICWP_WPSF_DataProcessor::ExtractCommaSeparatedList( $sOptionValue );
  451. }
  452. }
  453. $this->setOpt( $sOptionKey, $sOptionValue );
  454. }
  455. return $this->savePluginOptions( true );
  456. }
  457. /**
  458. * Should be over-ridden by each new class to handle upgrades.
  459. *
  460. * Called upon construction and after plugin options are initialized.
  461. */
  462. protected function updateHandler() {
  463. // if ( version_compare( $sCurrentVersion, '2.3.0', '<=' ) ) { }
  464. }
  465. /**
  466. * @param array $inaNewOptions
  467. */
  468. protected function mergeNonUiOptions( $inaNewOptions = array() ) {
  469. if ( !empty( $this->m_aNonUiOptions ) ) {
  470. $this->m_aNonUiOptions = array_merge( $this->m_aNonUiOptions, $inaNewOptions );
  471. }
  472. else {
  473. $this->m_aNonUiOptions = $inaNewOptions;
  474. }
  475. }
  476. /**
  477. * @return boolean
  478. */
  479. public function hasEncryptOption() {
  480. return function_exists( 'md5' );
  481. // return extension_loaded( 'mcrypt' );
  482. }
  483. protected function getVisitorIpAddress( $infAsLong = true ) {
  484. $this->loadDataProcessor();
  485. return ICWP_WPSF_DataProcessor::GetVisitorIpAddress( $infAsLong );
  486. }
  487. /**
  488. * Prefixes an option key only if it's needed
  489. *
  490. * @param $sKey
  491. * @return string
  492. */
  493. protected function prefixOptionKey( $sKey ) {
  494. return $this->doPrefix( $sKey, '_' );
  495. }
  496. /**
  497. * Will prefix and return any string with the unique plugin prefix.
  498. *
  499. * @param string $sSuffix
  500. * @param string $sGlue
  501. * @return string
  502. */
  503. public function doPrefix( $sSuffix = '', $sGlue = '-' ) {
  504. $sPrefix = $this->oPluginVo->getFullPluginPrefix( $sGlue );
  505. if ( $sSuffix == $sPrefix || strpos( $sSuffix, $sPrefix.$sGlue ) === 0 ) { //it already has the prefix
  506. return $sSuffix;
  507. }
  508. return sprintf( '%s%s%s', $sPrefix, empty($sSuffix)? '' : $sGlue, empty($sSuffix)? '' : $sSuffix );
  509. }
  510. /**
  511. * @param string $insExistingListKey
  512. * @param string $insFilterName
  513. * @return array|false
  514. */
  515. protected function processIpFilter( $insExistingListKey, $insFilterName ) {
  516. $aFilterIps = apply_filters( $insFilterName, array() );
  517. if ( empty( $aFilterIps ) ) {
  518. return false;
  519. }
  520. $aNewIps = array();
  521. foreach( $aFilterIps as $mKey => $sValue ) {
  522. if ( is_string( $mKey ) ) { //it's the IP
  523. $sIP = $mKey;
  524. $sLabel = $sValue;
  525. }
  526. else { //it's not an associative array, so the value is the IP
  527. $sIP = $sValue;
  528. $sLabel = '';
  529. }
  530. $aNewIps[ $sIP ] = $sLabel;
  531. }
  532. // now add and store the new IPs
  533. $aExistingIpList = $this->getOpt( $insExistingListKey );
  534. if ( !is_array( $aExistingIpList ) ) {
  535. $aExistingIpList = array();
  536. }
  537. $this->loadDataProcessor();
  538. $nNewAddedCount = 0;
  539. $aNewList = ICWP_WPSF_DataProcessor::Add_New_Raw_Ips( $aExistingIpList, $aNewIps, $nNewAddedCount );
  540. if ( $nNewAddedCount > 0 ) {
  541. $this->setOpt( $insExistingListKey, $aNewList );
  542. }
  543. }
  544. protected function loadDataProcessor() {
  545. if ( !class_exists('ICWP_WPSF_DataProcessor') ) {
  546. require_once( dirname(__FILE__).'/icwp-data-processor.php' );
  547. }
  548. }
  549. /**
  550. * @return ICWP_WpFunctions_WPSF
  551. */
  552. protected function loadWpFunctions() {
  553. return ICWP_WpFunctions_WPSF::GetInstance();
  554. }
  555. /**
  556. * @return ICWP_WpFilesystem_WPSF
  557. */
  558. protected function loadFileSystemProcessor() {
  559. if ( !class_exists('ICWP_WpFilesystem_WPSF') ) {
  560. require_once( dirname(__FILE__) . '/icwp-wpfilesystem.php' );
  561. }
  562. return ICWP_WpFilesystem_WPSF::GetInstance();
  563. }
  564. }
  565. endif;
  566. class ICWP_OptionsHandler_Base_Wpsf extends ICWP_OptionsHandler_Base_V2 { }