PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/connectors/class-wp-stream-connector-acf.php

https://gitlab.com/wp-stream/stream
PHP | 542 lines | 309 code | 60 blank | 173 comment | 27 complexity | 5e944709160c482acc24ce2e711bd426 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0
  1. <?php
  2. class WP_Stream_Connector_ACF extends WP_Stream_Connector {
  3. /**
  4. * Connector slug
  5. *
  6. * @var string
  7. */
  8. public static $name = 'acf';
  9. /**
  10. * Holds tracked plugin minimum version required
  11. *
  12. * @const string
  13. */
  14. const PLUGIN_MIN_VERSION = '4.3.8';
  15. /**
  16. * Actions registered for this connector
  17. *
  18. * @var array
  19. */
  20. public static $actions = array(
  21. 'added_post_meta',
  22. 'updated_post_meta',
  23. 'delete_post_meta',
  24. 'added_user_meta',
  25. 'updated_user_meta',
  26. 'delete_user_meta',
  27. 'added_option',
  28. 'updated_option',
  29. 'deleted_option',
  30. 'pre_post_update',
  31. );
  32. /**
  33. * Cached location rules, used in shutdown callback to verify changes in meta
  34. *
  35. * @var array
  36. */
  37. public static $cached_location_rules = array();
  38. /**
  39. * Cached field values updates, used by shutdown callback to verify actual changes
  40. *
  41. * @var array
  42. */
  43. public static $cached_field_values_updates = array();
  44. /**
  45. * Check if plugin dependencies are satisfied and add an admin notice if not
  46. *
  47. * @return bool
  48. */
  49. public static function is_dependency_satisfied() {
  50. if ( class_exists( 'acf' ) && version_compare( acf()->settings['version'], self::PLUGIN_MIN_VERSION, '>=' ) ) {
  51. return true;
  52. }
  53. return false;
  54. }
  55. /**
  56. * Return translated connector label
  57. *
  58. * @return string Translated connector label
  59. */
  60. public static function get_label() {
  61. return _x( 'ACF', 'acf', 'stream' );
  62. }
  63. /**
  64. * Return translated action labels
  65. *
  66. * @return array Action label translations
  67. */
  68. public static function get_action_labels() {
  69. return array(
  70. 'created' => __( 'Created', 'acf', 'stream' ),
  71. 'updated' => __( 'Updated', 'acf', 'stream' ),
  72. 'added' => __( 'Added', 'acf', 'stream' ),
  73. 'deleted' => __( 'Deleted', 'acf', 'stream' ),
  74. );
  75. }
  76. /**
  77. * Return translated context labels
  78. *
  79. * @return array Context label translations
  80. */
  81. public static function get_context_labels() {
  82. return array(
  83. 'field_groups' => _x( 'Field Groups', 'acf', 'stream' ),
  84. 'fields' => _x( 'Fields', 'acf', 'stream' ),
  85. 'rules' => _x( 'Rules', 'acf', 'stream' ),
  86. 'options' => _x( 'Options', 'acf', 'stream' ),
  87. 'values' => _x( 'Values', 'acf', 'stream' ),
  88. );
  89. }
  90. /**
  91. * Register the connector
  92. *
  93. * @return void
  94. */
  95. public static function register() {
  96. add_filter( 'wp_stream_log_data', array( __CLASS__, 'log_override' ) );
  97. /**
  98. * Allow devs to disable logging values of rendered forms
  99. *
  100. * @return bool
  101. */
  102. if ( apply_filters( 'wp_stream_acf_enable_value_logging', true ) ) {
  103. self::$actions[] = 'acf/update_value';
  104. }
  105. parent::register();
  106. }
  107. /**
  108. * Add action links to Stream drop row in admin list screen
  109. *
  110. * @filter wp_stream_action_links_{connector}
  111. *
  112. * @param array $links Previous links registered
  113. * @param object $record Stream record
  114. *
  115. * @return array Action links
  116. */
  117. public static function action_links( $links, $record ) {
  118. $links = WP_Stream_Connector_Posts::action_links( $links, $record );
  119. return $links;
  120. }
  121. /**
  122. * Track addition of post meta
  123. *
  124. * @action added_post_meta
  125. */
  126. public static function callback_added_post_meta() {
  127. call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'post', 'added' ), func_get_args() ) );
  128. }
  129. /**
  130. * Track updating post meta
  131. *
  132. * @action updated_post_meta
  133. */
  134. public static function callback_updated_post_meta() {
  135. call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'post', 'updated' ), func_get_args() ) );
  136. }
  137. /**
  138. * Track deletion of post meta
  139. *
  140. * Note: Using delete_post_meta instead of deleted_post_meta to be able to
  141. * capture old field value
  142. *
  143. * @action delete_post_meta
  144. */
  145. public static function callback_delete_post_meta() {
  146. call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'post', 'deleted' ), func_get_args() ) );
  147. }
  148. /**
  149. * Track addition of user meta
  150. *
  151. * @action added_user_meta
  152. */
  153. public static function callback_added_user_meta() {
  154. call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'user', 'added' ), func_get_args() ) );
  155. }
  156. /**
  157. * Track updating user meta
  158. *
  159. * @action updated_user_meta
  160. */
  161. public static function callback_updated_user_meta() {
  162. call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'user', 'updated' ), func_get_args() ) );
  163. }
  164. /**
  165. * Track deletion of user meta
  166. *
  167. * Note: Using delete_user_meta instead of deleted_user_meta to be able to
  168. * capture old field value
  169. *
  170. * @action delete_user_meta
  171. */
  172. public static function callback_delete_user_meta() {
  173. call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'user', 'deleted' ), func_get_args() ) );
  174. }
  175. /**
  176. * Track addition of post/user meta
  177. *
  178. * @param string $type Type of object, post or user
  179. * @param string $action Added, updated, deleted
  180. * @param integer $meta_id
  181. * @param integer $object_id
  182. * @param string $meta_key
  183. * @param mixed|null $meta_value
  184. */
  185. public static function check_meta( $type, $action, $meta_id, $object_id, $meta_key, $meta_value = null ) {
  186. if ( 'post' !== $type || ! ( $post = get_post( $object_id ) ) || 'acf' !== $post->post_type ) {
  187. self::check_meta_values( $type, $action, $meta_id, $object_id, $meta_key, $meta_value = null );
  188. return;
  189. }
  190. $action_labels = self::get_action_labels();
  191. // Fields
  192. if ( 0 === strpos( $meta_key, 'field_' ) ) {
  193. if ( 'deleted' === $action ) {
  194. $meta_value = get_post_meta( $object_id, $meta_key, true );
  195. }
  196. self::log(
  197. _x( '"%1$s" field in "%2$s" %3$s', 'acf', 'stream' ),
  198. array(
  199. 'label' => $meta_value['label'],
  200. 'title' => $post->post_title,
  201. 'action' => strtolower( $action_labels[ $action ] ),
  202. 'key' => $meta_value['key'],
  203. 'name' => $meta_value['name'],
  204. ),
  205. $object_id,
  206. 'fields',
  207. $action
  208. );
  209. }
  210. // Location rules
  211. elseif ( 'rule' === $meta_key ) {
  212. if ( 'deleted' === $action ) {
  213. self::$cached_location_rules[ $object_id ] = get_post_meta( $object_id, 'rule' );
  214. add_action( 'shutdown', array( __CLASS__, 'check_location_rules' ), 9 );
  215. }
  216. }
  217. // Position option
  218. elseif ( 'position' === $meta_key ) {
  219. if ( 'deleted' === $action ) {
  220. return;
  221. }
  222. $options = array(
  223. 'acf_after_title' => _x( 'High (after title)', 'acf', 'stream' ),
  224. 'normal' => _x( 'Normal (after content)', 'acf', 'stream' ),
  225. 'side' => _x( 'Side', 'acf', 'stream' ),
  226. );
  227. self::log(
  228. _x( 'Position of "%1$s" updated to "%2$s"', 'acf', 'stream' ),
  229. array(
  230. 'title' => $post->post_title,
  231. 'option_label' => $options[ $meta_value ],
  232. 'option' => $meta_key,
  233. 'option_value' => $meta_value,
  234. ),
  235. $object_id,
  236. 'options',
  237. 'updated'
  238. );
  239. }
  240. // Layout option
  241. elseif ( 'layout' === $meta_key ) {
  242. if ( 'deleted' === $action ) {
  243. return;
  244. }
  245. $options = array(
  246. 'no_box' => _x( 'Seamless (no metabox)', 'acf', 'stream' ),
  247. 'default' => _x( 'Standard (WP metabox)', 'acf', 'stream' ),
  248. );
  249. self::log(
  250. _x( 'Style of "%1$s" updated to "%2$s"', 'acf', 'stream' ),
  251. array(
  252. 'title' => $post->post_title,
  253. 'option_label' => $options[ $meta_value ],
  254. 'option' => $meta_key,
  255. 'option_value' => $meta_value,
  256. ),
  257. $object_id,
  258. 'options',
  259. 'updated'
  260. );
  261. }
  262. // Screen exclusion option
  263. elseif ( 'hide_on_screen' === $meta_key ) {
  264. if ( 'deleted' === $action ) {
  265. return;
  266. }
  267. $options = array(
  268. 'permalink' => _x( 'Permalink', 'acf', 'stream' ),
  269. 'the_content' => _x( 'Content Editor', 'acf', 'stream' ),
  270. 'excerpt' => _x( 'Excerpt', 'acf', 'stream' ),
  271. 'custom_fields' => _x( 'Custom Fields', 'acf', 'stream' ),
  272. 'discussion' => _x( 'Discussion', 'acf', 'stream' ),
  273. 'comments' => _x( 'Comments', 'acf', 'stream' ),
  274. 'revisions' => _x( 'Revisions', 'acf', 'stream' ),
  275. 'slug' => _x( 'Slug', 'acf', 'stream' ),
  276. 'author' => _x( 'Author', 'acf', 'stream' ),
  277. 'format' => _x( 'Format', 'acf', 'stream' ),
  278. 'featured_image' => _x( 'Featured Image', 'acf', 'stream' ),
  279. 'categories' => _x( 'Categories', 'acf', 'stream' ),
  280. 'tags' => _x( 'Tags', 'acf', 'stream' ),
  281. 'send-trackbacks' => _x( 'Send Trackbacks', 'acf', 'stream' ),
  282. );
  283. if ( count( $options ) === count( $meta_value ) ) {
  284. $options_label = _x( 'All screens', 'acf', 'stream' );
  285. } elseif ( empty( $meta_value ) ) {
  286. $options_label = _x( 'No screens', 'acf', 'stream' );
  287. } else {
  288. $options_label = implode( ', ', array_intersect_key( $options, array_flip( $meta_value ) ) );
  289. }
  290. self::log(
  291. _x( '"%1$s" set to display on "%2$s"', 'acf', 'stream' ),
  292. array(
  293. 'title' => $post->post_title,
  294. 'option_label' => $options_label,
  295. 'option' => $meta_key,
  296. 'option_value' => $meta_value,
  297. ),
  298. $object_id,
  299. 'options',
  300. 'updated'
  301. );
  302. }
  303. }
  304. /**
  305. * Track changes to ACF values within rendered post meta forms
  306. *
  307. * @param string $type Type of object, post or user
  308. * @param string $action Added, updated, deleted
  309. * @param integer $meta_id
  310. * @param integer $object_id
  311. * @param string $key
  312. * @param mixed|null $value
  313. *
  314. * @return bool
  315. */
  316. public static function check_meta_values( $type, $action, $meta_id, $object_id, $key, $value = null ) {
  317. if ( empty( self::$cached_field_values_updates ) ) {
  318. return false;
  319. }
  320. $object_key = $object_id;
  321. if ( 'user' === $type ) {
  322. $object_key = 'user_' . $object_id;
  323. } elseif ( 'taxonomy' === $type ) {
  324. if ( 0 === strpos( $key, '_' ) ) { // Ignore the 'revision' stuff!
  325. return false;
  326. }
  327. if ( 1 !== preg_match( '#([a-z0-9_-]+)_([\d]+)_([a-z0-9_-]+)#', $key, $matches ) ) {
  328. return false;
  329. }
  330. list( , $taxonomy, $term_id, $key ) = $matches; // Skips 0 index
  331. $object_key = $taxonomy . '_' . $term_id;
  332. }
  333. if ( isset( self::$cached_field_values_updates[ $object_key ][ $key ] ) ) {
  334. if ( 'post' === $type ) {
  335. $post = get_post( $object_id );
  336. $title = $post->post_title;
  337. $type_name = strtolower( WP_Stream_Connector_Posts::get_post_type_name( $post->post_type ) );
  338. } elseif ( 'user' === $type ) {
  339. $user = new WP_User( $object_id );
  340. $title = $user->get( 'display_name' );
  341. $type_name = __( 'user', 'stream' );
  342. } elseif ( 'taxonomy' === $type ) {
  343. $term = get_term( $term_id, $taxonomy );
  344. $title = $term->name;
  345. $tax_obj = get_taxonomy( $taxonomy );
  346. $type_name = strtolower( get_taxonomy_labels( $tax_obj )->singular_name );
  347. } else {
  348. return false;
  349. }
  350. $cache = self::$cached_field_values_updates[ $object_key ][ $key ];
  351. self::log(
  352. _x( '"%1$s" of "%2$s" %3$s updated', 'acf', 'stream' ),
  353. array(
  354. 'field_label' => $cache['field']['label'],
  355. 'title' => $title,
  356. 'singular_name' => $type_name,
  357. 'meta_value' => $value,
  358. 'meta_key' => $key,
  359. 'meta_type' => $type,
  360. ),
  361. $object_id,
  362. 'values',
  363. 'updated'
  364. );
  365. }
  366. return true;
  367. }
  368. /**
  369. * Track changes to rules, complements post-meta updates
  370. *
  371. * @action shutdown
  372. */
  373. public static function check_location_rules() {
  374. foreach ( self::$cached_location_rules as $post_id => $old ) {
  375. $new = get_post_meta( $post_id, 'rule' );
  376. $post = get_post( $post_id );
  377. if ( $old === $new ) {
  378. continue;
  379. }
  380. $new = array_map( 'json_encode', $new );
  381. $old = array_map( 'json_encode', $old );
  382. $added = array_diff( $new, $old );
  383. $deleted = array_diff( $old, $new );
  384. self::log(
  385. _x( 'Updated rules of "%1$s" (%2$d added, %3$d deleted)', 'acf', 'stream' ),
  386. array(
  387. 'title' => $post->post_title,
  388. 'no_added' => count( $added ),
  389. 'no_deleted' => count( $deleted ),
  390. 'added' => $added,
  391. 'deleted' => $deleted,
  392. ),
  393. $post_id,
  394. 'rules',
  395. 'updated'
  396. );
  397. }
  398. }
  399. /**
  400. * Override connector log for our own Settings / Actions
  401. *
  402. * @param array $data
  403. *
  404. * @return array|bool
  405. */
  406. public static function log_override( $data ) {
  407. if ( ! is_array( $data ) ) {
  408. return $data;
  409. }
  410. if ( 'posts' === $data['connector'] && 'acf' === $data['context'] ) {
  411. $data['context'] = 'field_groups';
  412. $data['connector'] = self::$name;
  413. $data['args']['singular_name'] = __( 'field group', 'stream' );
  414. }
  415. return $data;
  416. }
  417. /**
  418. * Track changes to custom field values updates, saves filtered values to be
  419. * processed by callback_updated_post_meta
  420. *
  421. * @param $value
  422. * @param $post_id
  423. * @param $field
  424. */
  425. public static function callback_acf_update_value( $value, $post_id, $field ) {
  426. self::$cached_field_values_updates[ $post_id ][ $field['name'] ] = compact( 'field', 'value', 'post_id' );
  427. return $value;
  428. }
  429. /**
  430. * Track changes to post main attributes, ie: Order No.
  431. *
  432. * @param $post_id
  433. * @param $data Array with the updated post data
  434. */
  435. public static function callback_pre_post_update( $post_id, $data ) {
  436. $post = get_post( $post_id );
  437. if ( 'acf' !== $post->post_type ) {
  438. return;
  439. }
  440. // menu_order, aka Order No.
  441. if ( $data['menu_order'] !== $post->menu_order ) {
  442. self::log(
  443. _x( 'Updated Order of "%1$s" from %2$d to %3$d', 'acf', 'stream' ),
  444. array(
  445. 'title' => $post->post_title,
  446. 'old_menu_order' => $post->menu_order,
  447. 'menu_order' => $data['menu_order'],
  448. ),
  449. $post_id,
  450. 'field_groups',
  451. 'updated'
  452. );
  453. }
  454. }
  455. /**
  456. * Track addition of new options
  457. *
  458. * @param $key Option name
  459. * @param $value Option value
  460. */
  461. public static function callback_added_option( $key, $value ) {
  462. self::check_meta_values( 'taxonomy', 'added', null, null, $key, $value );
  463. }
  464. /**
  465. * Track addition of new options
  466. *
  467. * @param $key
  468. * @param $old
  469. * @param $value
  470. */
  471. public static function callback_updated_option( $key, $old, $value ) {
  472. self::check_meta_values( 'taxonomy', 'updated', null, null, $key, $value );
  473. }
  474. /**
  475. * Track addition of new options
  476. *
  477. * @param $key
  478. */
  479. public static function callback_deleted_option( $key ) {
  480. self::check_meta_values( 'taxonomy', 'deleted', null, null, $key, null );
  481. }
  482. }