PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/themes/shapely/inc/libraries/epsilon-framework/classes/backend/class-epsilon-content-backup.php

https://gitlab.com/chernushov881/charity-fund
PHP | 620 lines | 360 code | 77 blank | 183 comment | 48 complexity | ee2c5651376aeb0dca7685b262aba55b MD5 | raw file
  1. <?php
  2. if ( ! defined( 'WPINC' ) ) {
  3. die;
  4. }
  5. /**
  6. * @since 1.0.0
  7. *
  8. * Class Epsilon_Content_Backup
  9. */
  10. class Epsilon_Content_Backup {
  11. /**
  12. * @since 1.0.0
  13. * @var array
  14. */
  15. public $fields = array();
  16. /**
  17. * @since 1.0.0
  18. * @var string
  19. */
  20. public $slug = '';
  21. /**
  22. * @since 1.0.0
  23. * @var int
  24. */
  25. public $setting_page;
  26. /**
  27. * @since 1.2.0
  28. * @var
  29. */
  30. public $pages = array();
  31. /**
  32. * @since 1.0.0
  33. * @var string
  34. */
  35. public $hash;
  36. /**
  37. * @since 1.0.0
  38. * @var string
  39. */
  40. public $mode = 'theme_mods';
  41. /**
  42. * @since 1.2.0
  43. * @var WordPress Manager Object
  44. */
  45. public $manager = null;
  46. /**
  47. * The single instance of the backup class
  48. *
  49. * @var object
  50. * @access private
  51. * @since 1.2.0
  52. */
  53. private static $_instance = null;
  54. /**
  55. * @since 1.0.0
  56. * Epsilon_Content_Backup constructor.
  57. */
  58. public function __construct() {
  59. $theme = wp_get_theme();
  60. $this->slug = get_stylesheet();
  61. $this->setting_page = get_option( $this->slug . '_backup_settings', false );
  62. $this->hash = $this->calculate_hash();
  63. if ( ! $this->setting_page || null === get_post( $this->setting_page ) ) {
  64. $args = array(
  65. 'post_title' => $theme->get( 'Name' ) . ' Backup Settings',
  66. 'post_status' => 'draft',
  67. 'post_type' => 'page',
  68. 'post_author' => 0,
  69. );
  70. $this->setting_page = wp_insert_post( $args );
  71. if ( ! is_wp_error( $this->setting_page ) ) {
  72. update_option( $this->slug . '_backup_settings', $this->setting_page );
  73. }
  74. }
  75. /**
  76. * Add a notice, inform user that this page is only for backup purposes
  77. */
  78. add_action( 'add_meta_boxes', array( $this, 'add_notice_to_page' ), 10, 2 );
  79. /**
  80. * We need to keep this page as draft, forever.
  81. */
  82. add_action( 'admin_init', array( $this, 'check_page_status' ) );
  83. /**
  84. * We need to use this hook so we have a reference of the fields that are required as back up
  85. */
  86. add_action( 'customize_save_after', array( $this, 'backup_settings' ) );
  87. /**
  88. * Save page builder
  89. */
  90. add_action( 'customize_save_after', array( $this, 'save_page_builder' ) );
  91. /**
  92. * Disable the form editor if we're in production
  93. */
  94. add_action( 'edit_form_after_title', array( $this, 'disable_front_page_editor' ) );
  95. }
  96. /**
  97. * @since 1.0.0
  98. * @return Epsilon_Content_Backup
  99. */
  100. public static function get_instance() {
  101. if ( is_null( self::$_instance ) ) {
  102. self::$_instance = new self();
  103. }
  104. return self::$_instance;
  105. }
  106. /**
  107. * Registers a field in the "backup" collection
  108. *
  109. * @since 1.0.0
  110. */
  111. public function add_field( $id, $args ) {
  112. $this->fields[ $id ] = $args;
  113. }
  114. /**
  115. * Pages who should be backed up
  116. *
  117. * @param $page_id
  118. * @param $id
  119. * @param $args
  120. */
  121. public function add_pages( $page_id, $id, $args ) {
  122. $this->pages[ $page_id ] = array(
  123. 'id' => $page_id,
  124. 'field_id' => $id,
  125. 'fields' => $args,
  126. );
  127. }
  128. /**
  129. * Disables the frontend editor
  130. *
  131. * @since 1.3.4
  132. */
  133. public function disable_front_page_editor( $post ) {
  134. if ( true === WP_DEBUG ) {
  135. return false;
  136. }
  137. if ( $this->setting_page == $post->ID ) {
  138. remove_post_type_support( $post->post_type, 'editor' );
  139. }
  140. }
  141. /**
  142. * Calculates the hash of the settings
  143. */
  144. private function calculate_hash() {
  145. $hash = array(
  146. 'theme_mods' => md5( json_encode( get_option( 'theme_mods_' . $this->slug ) ) ),
  147. 'post_meta' => md5( json_encode( get_post_meta( $this->setting_page ) ) ),
  148. );
  149. return $hash;
  150. }
  151. /**
  152. * Check the status of the settings page, it should always be draft
  153. *
  154. * @since 1.0.0
  155. */
  156. public function check_page_status() {
  157. $post = get_post( $this->setting_page );
  158. if ( 'draft' !== $post->post_status ) {
  159. $settings = array(
  160. 'ID' => $this->setting_page,
  161. 'post_status' => 'draft',
  162. );
  163. wp_update_post( $settings );
  164. }
  165. }
  166. /**
  167. * Adds a notice to the page
  168. *
  169. * @since 1.0.0
  170. */
  171. public function add_notice_to_page( $post_type, $post ) {
  172. $continue = false;
  173. if ( 'page' !== $post_type ) {
  174. return;
  175. }
  176. /**
  177. * Need to make sure we are in a page that has content saved in the customizer
  178. *
  179. * Verify the last element of the explode meta,
  180. * if it's the same as the post ID it means we're doing it right
  181. */
  182. $meta = get_post_meta( $post->ID );
  183. foreach ( $meta as $key => $value ) {
  184. $key = explode( '_', $key );
  185. if ( end( $key ) === $post->ID ) {
  186. $continue = true;
  187. }
  188. }
  189. if ( $this->setting_page === $post->ID ) {
  190. $continue = true;
  191. }
  192. if ( ! $continue ) {
  193. return;
  194. }
  195. $notifications = Epsilon_Notifications::get_instance();
  196. $notifications->add_notice(
  197. array(
  198. 'id' => $this->slug . '_content_backup',
  199. 'type' => 'notice notice-info',
  200. 'message' => '<p>' . esc_html__( 'This page contains the content created by the customizer.', 'epsilon-framework' ) . '</p>',
  201. )
  202. );
  203. }
  204. /**
  205. * @since 1.0.0
  206. *
  207. * @param $manager WordPress Customizer Manager
  208. */
  209. public function backup_settings( $manager ) {
  210. $check = $this->check_hash();
  211. $this->manager = $manager;
  212. if ( $check['status'] ) {
  213. return;
  214. };
  215. $settings = array(
  216. 'ID' => $this->setting_page,
  217. 'post_content' => $this->parse_content(),
  218. );
  219. wp_update_post( $settings );
  220. }
  221. /**
  222. * @since 1.0.0
  223. *
  224. * @param $manager WordPress Customizer Manager
  225. */
  226. public function save_page_builder( $manager ) {
  227. $this->manager = $manager;
  228. $check = $this->check_advanced_hash();
  229. $this->mode = 'post_meta';
  230. foreach ( $this->pages as $page ) {
  231. if ( $check[ $page['id'] ]['status'] ) {
  232. continue;
  233. };
  234. $meta = get_post_meta( $page['id'], $page['field_id'], true );
  235. if ( empty( $meta[ $page['field_id'] ] ) ) {
  236. continue;
  237. }
  238. $settings = array(
  239. 'ID' => $page['id'],
  240. 'post_content' => $this->parse_content_advanced( $page ),
  241. );
  242. wp_update_post( $settings );
  243. };
  244. }
  245. /**
  246. * @since 1.0.0
  247. * @return array
  248. */
  249. private function check_hash() {
  250. $arr = array(
  251. 'status' => true,
  252. );
  253. $temp = reset( $this->fields );
  254. /**
  255. * In case we save options as post_meta, we need to use that particular hash
  256. */
  257. if ( is_array( $temp ) && isset( $temp['save_as_meta'] ) && $this->setting_page === $temp['save_as_meta'] ) {
  258. $this->mode = 'post_meta';
  259. }
  260. $last_known_hash = get_transient( $this->slug . '_hash_update' );
  261. if ( false === $last_known_hash ) {
  262. set_transient( $this->slug . '_hash_update', $this->hash[ $this->mode ], 5 * MINUTE_IN_SECONDS );
  263. }
  264. if ( $last_known_hash !== $this->hash[ $this->mode ] ) {
  265. $arr['status'] = false;
  266. }
  267. return $arr;
  268. }
  269. /**
  270. * @since 1.0.0
  271. * @return array
  272. */
  273. private function check_advanced_hash() {
  274. $arr = array();
  275. foreach ( $this->pages as $page ) {
  276. $arr[ $page['id'] ] = array(
  277. 'status' => true,
  278. );
  279. $last_known_hash = get_transient( $this->slug . '_' . $page['id'] . '_hash_update' );
  280. $hash = md5( json_encode( get_post_meta( $page['id'] ) ) );
  281. if ( false === $last_known_hash ) {
  282. set_transient( $this->slug . '_' . $page['id'] . '_hash_update', $hash, 5 * MINUTE_IN_SECONDS );
  283. }
  284. if ( $last_known_hash !== $hash ) {
  285. $arr[ $page['id'] ] = array(
  286. 'status' => false,
  287. );
  288. }
  289. }
  290. return $arr;
  291. }
  292. /**
  293. * @since 1.2.0
  294. * @return string
  295. */
  296. private function parse_content_advanced( $page ) {
  297. $content = '';
  298. $collection = array();
  299. $options = get_post_meta( $page['id'] );
  300. foreach ( $page['fields'] as $field ) {
  301. $collection[ $page['field_id'] ] = array(
  302. 'id' => $page['field_id'],
  303. 'content' => get_post_meta( $page['id'], $page['field_id'], true ),
  304. 'type' => 'epsilon-section-repeater',
  305. );
  306. }
  307. foreach ( $collection as $field => $props ) {
  308. $content .= $this->_parse_content( $props );
  309. }
  310. return $content;
  311. }
  312. /**
  313. * @since 1.0.0
  314. * @return string
  315. */
  316. private function parse_content() {
  317. $content = '';
  318. $collection = array();
  319. switch ( $this->mode ) {
  320. case 'post_meta':
  321. $options = get_post_meta( $this->setting_page );
  322. foreach ( $this->fields as $id => $field ) {
  323. if ( array_key_exists( $id, $options ) ) {
  324. $collection[ $id ] = array(
  325. 'id' => $id,
  326. 'content' => get_post_meta( $this->setting_page, $id, true ),
  327. 'type' => $field['type'],
  328. );
  329. }
  330. }
  331. break;
  332. default:
  333. $options = get_option( 'theme_mods_' . $this->slug );
  334. foreach ( $this->fields as $id => $field ) {
  335. if ( array_key_exists( $id, $options ) ) {
  336. $collection[ $id ] = array(
  337. 'id' => $id,
  338. 'content' => get_theme_mod( $id ),
  339. 'type' => $field['type'],
  340. );
  341. }
  342. }
  343. break;
  344. }
  345. foreach ( $collection as $field => $props ) {
  346. $content .= $this->_parse_content( $props );
  347. }
  348. return $content;
  349. }
  350. /**
  351. * @since 1.0.0
  352. * @return string;
  353. */
  354. private function _parse_content( $field ) {
  355. if ( empty( $field['content'] ) ) {
  356. return '';
  357. }
  358. $control = $this->manager->get_control( $field['id'] );
  359. $content = '';
  360. if ( 'post_meta' === $this->mode ) {
  361. $field['content'] = $field['content'][ $field['id'] ];
  362. }
  363. switch ( $field['type'] ) {
  364. case 'epsilon-section-repeater':
  365. foreach ( $field['content'] as $single_section ) {
  366. $content .= '<!-- epsilon/' . $control->repeatable_sections[ $single_section['type'] ]['id'] . ' -->' . "\n";
  367. foreach ( $single_section as $id => $val ) {
  368. $args = array(
  369. 'val' => $val,
  370. 'id' => $id,
  371. 'fields' => $control->repeatable_sections[ $single_section['type'] ]['fields'],
  372. );
  373. $condition = $this->check_backup_condition( $args );
  374. if ( ! $condition ) {
  375. continue;
  376. }
  377. $content .= $this->create_content_value( $args['val'], $args['fields'][ $id ]['type'] );
  378. }
  379. $content .= '<!-- /epsilon/' . $control->repeatable_sections[ $single_section['type'] ]['id'] . ' -->' . "\n \n";
  380. }
  381. $content .= "\n \n";
  382. break;
  383. case 'epsilon-repeater':
  384. $content .= $this->_format_default( $control, $field['content'], $control->id );
  385. break;
  386. default:
  387. $content .= "\n";
  388. $content .= $field['content'];
  389. $content .= "\n \n";
  390. break;
  391. }// End switch().
  392. return $content;
  393. }
  394. /**
  395. * Checks if we need to generate backup for this item
  396. *
  397. * @param $args Array Array of arguments.
  398. *
  399. * @return bool
  400. */
  401. private function check_backup_condition( $args ) {
  402. /**
  403. * Empty values don't need to be saved
  404. */
  405. if ( empty( $args['val'] ) ) {
  406. return false;
  407. }
  408. /**
  409. * Id of the field doesn't need saving
  410. */
  411. if ( 'type' === $args['id'] ) {
  412. return false;
  413. }
  414. /**
  415. * Design related items should not be saved
  416. */
  417. $skip = array(
  418. 'epsilon-customizer-navigation',
  419. 'epsilon-icon-picker',
  420. 'epsilon-toggle',
  421. 'epsilon-slider',
  422. 'epsilon-color-picker',
  423. 'select',
  424. 'selectize',
  425. 'epsilon-button-group',
  426. );
  427. /**
  428. * Customization fields, should bot be backedup
  429. */
  430. if ( ! array_key_exists( $args['id'], $args['fields'] ) ) {
  431. return false;
  432. }
  433. if ( in_array( $args['fields'][ $args['id'] ]['type'], $skip ) ) {
  434. return false;
  435. }
  436. /**
  437. * If conditions are false, we return true
  438. */
  439. return true;
  440. }
  441. /**
  442. * Parse the value and create "readable" content.
  443. *
  444. *
  445. * @param $value array|string Can be both.
  446. * @param $type string Type of field we are saving content.
  447. *
  448. * @return string
  449. */
  450. private function create_content_value( $value, $type ) {
  451. switch ( $type ) {
  452. case 'epsilon-image':
  453. return '<img src="' . $value . '" />' . "\n";
  454. break;
  455. case 'hidden':
  456. $control = $this->manager->get_control( $value );
  457. if ( is_a( $control, 'Epsilon_Control_Repeater' ) ) {
  458. switch ( $this->mode ) {
  459. case 'post_meta':
  460. $val = get_post_meta( $this->setting_page, $value, true );
  461. if ( empty( $val ) ) {
  462. return $val;
  463. }
  464. if ( ! isset( $val[ $value ] ) ) {
  465. return $val;
  466. }
  467. $val = $val[ $value ];
  468. break;
  469. default:
  470. $val = get_theme_mod( $value, array() );
  471. break;
  472. }
  473. $content = '';
  474. $content .= $this->format_block( $control, $val, $control->id );
  475. return $content;
  476. };// End if().
  477. return '';
  478. break;
  479. default:
  480. return $value . "\n";
  481. break;
  482. }// End switch().
  483. }
  484. /**
  485. * Formats the repeater field HTML as per outside (if given) instructions
  486. *
  487. * @since 1.3.4
  488. *
  489. * @param $control
  490. * @param $val
  491. * @param $id
  492. */
  493. private function format_block( $control, $value, $id ) {
  494. $parser = $this->slug . '_post_parser';
  495. if ( ! class_exists( $parser ) ) {
  496. return $this->_format_default( $control, $value, $id );
  497. }
  498. $parser = $parser::get_instance();
  499. $method = 'parse_' . $id;
  500. if ( ! method_exists( $parser, $method ) ) {
  501. return $this->_format_default( $control, $value, $id );
  502. }
  503. return $parser->$method( $control, $value, $id );
  504. }
  505. /**
  506. * Provides a fallback for the content block formatting
  507. *
  508. * @since 1.3.4
  509. *
  510. * @param $control
  511. * @param $value
  512. * @param $id
  513. *
  514. * @return string
  515. */
  516. private function _format_default( $control, $value, $id ) {
  517. $content = '';
  518. foreach ( $value as $fields ) {
  519. $content .= '<!-- epsilon/' . $control->label . ' -->' . "\n";
  520. foreach ( $fields as $id => $f_val ) {
  521. if ( empty( $f_val ) ) {
  522. continue;
  523. }
  524. if ( ! isset( $control->fields[ $id ] ) ) {
  525. continue;
  526. }
  527. if ( ! isset( $control->fields[ $id ]['type'] ) ) {
  528. continue;
  529. }
  530. if ( 'epsilon-color-picker' === $control->fields[ $id ]['type'] || 'epsilon-icon-picker' === $control->fields[ $id ]['type'] ) {
  531. continue;
  532. };
  533. if ( 'epsilon-image' === $control->fields[ $id ]['type'] ) {
  534. $content .= '<img src="' . $f_val . '" />' . "\n";
  535. } else {
  536. $content .= $f_val . "\n";
  537. }
  538. }
  539. $content .= '<!-- /epsilon/' . $control->label . '-->' . "\n";
  540. }
  541. return $content;
  542. }
  543. }