From aeb3120729d944fd98bad574a4ffac2898e6cebc Mon Sep 17 00:00:00 2001 From: Jared Cobb Date: Sat, 16 Dec 2017 08:48:14 -0700 Subject: [PATCH 01/16] Moving class files, remove parent class, rename fields, WIP --- README.txt | 17 +- admin/class-ccb-core-admin.php | 117 +----- admin/class-ccb-core-settings-field.php | 283 -------------- ccb-core.php | 30 +- {admin/css => css}/ccb-core-admin.css | 0 {admin/css => css}/vendor/default.css | 0 {admin/css => css}/vendor/default.date.css | 0 {admin/css => css}/vendor/powerange.min.css | 0 {admin/css => css}/vendor/switchery.min.css | 0 {admin/css => css}/vendor/tipr.css | 0 includes/class-ccb-core-activator.php | 6 +- includes/class-ccb-core-helpers.php | 138 +++++++ includes/class-ccb-core-i18n.php | 43 --- includes/class-ccb-core-loader.php | 128 ------- includes/class-ccb-core-plugin.php | 58 +-- includes/class-ccb-core-settings-field.php | 347 +++++++++++++++++ .../class-ccb-core-settings-page.php | 25 +- .../class-ccb-core-settings-section.php | 69 ++-- .../class-ccb-core-settings.php | 358 +++++++++--------- {admin => includes}/class-ccb-core-sync.php | 8 +- includes/class-ccb-core.php | 291 +++++++++----- .../post-types/class-ccb-core-calendar.php | 70 ++++ includes/post-types/class-ccb-core-cpt.php | 129 +++++++ .../post-types}/class-ccb-core-cpts.php | 4 +- includes/post-types/class-ccb-core-group.php | 70 ++++ .../class-ccb-core-calendar-event-type.php | 65 ++++ .../class-ccb-core-calendar-group-name.php | 65 ++++ .../class-ccb-core-calendar-grouping-name.php | 65 ++++ .../taxonomies/class-ccb-core-group-area.php | 65 ++++ .../taxonomies/class-ccb-core-group-day.php | 63 +++ .../class-ccb-core-group-department.php | 65 ++++ .../taxonomies/class-ccb-core-group-tag.php | 65 ++++ .../taxonomies/class-ccb-core-group-time.php | 65 ++++ .../taxonomies/class-ccb-core-group-type.php | 65 ++++ .../taxonomies/class-ccb-core-taxonomy.php | 56 +++ {admin/js => js}/ccb-core-admin.js | 9 +- {admin/js => js}/vendor/picker.date.js | 0 {admin/js => js}/vendor/picker.js | 0 {admin/js => js}/vendor/powerange.min.js | 0 {admin/js => js}/vendor/switchery.min.js | 0 {admin/js => js}/vendor/tipr.min.js | 0 lib/Encryption/Encryption.php | 163 -------- lib/class-ccb-core-vendor-encryption.php | 190 ++++++++++ 43 files changed, 2056 insertions(+), 1136 deletions(-) delete mode 100644 admin/class-ccb-core-settings-field.php rename {admin/css => css}/ccb-core-admin.css (100%) rename {admin/css => css}/vendor/default.css (100%) rename {admin/css => css}/vendor/default.date.css (100%) rename {admin/css => css}/vendor/powerange.min.css (100%) rename {admin/css => css}/vendor/switchery.min.css (100%) rename {admin/css => css}/vendor/tipr.css (100%) create mode 100644 includes/class-ccb-core-helpers.php delete mode 100644 includes/class-ccb-core-i18n.php delete mode 100644 includes/class-ccb-core-loader.php create mode 100644 includes/class-ccb-core-settings-field.php rename {admin => includes}/class-ccb-core-settings-page.php (65%) rename {admin => includes}/class-ccb-core-settings-section.php (65%) rename {admin => includes}/class-ccb-core-settings.php (59%) rename {admin => includes}/class-ccb-core-sync.php (99%) create mode 100644 includes/post-types/class-ccb-core-calendar.php create mode 100644 includes/post-types/class-ccb-core-cpt.php rename {admin => includes/post-types}/class-ccb-core-cpts.php (99%) create mode 100644 includes/post-types/class-ccb-core-group.php create mode 100644 includes/taxonomies/class-ccb-core-calendar-event-type.php create mode 100644 includes/taxonomies/class-ccb-core-calendar-group-name.php create mode 100644 includes/taxonomies/class-ccb-core-calendar-grouping-name.php create mode 100644 includes/taxonomies/class-ccb-core-group-area.php create mode 100644 includes/taxonomies/class-ccb-core-group-day.php create mode 100644 includes/taxonomies/class-ccb-core-group-department.php create mode 100644 includes/taxonomies/class-ccb-core-group-tag.php create mode 100644 includes/taxonomies/class-ccb-core-group-time.php create mode 100644 includes/taxonomies/class-ccb-core-group-type.php create mode 100644 includes/taxonomies/class-ccb-core-taxonomy.php rename {admin/js => js}/ccb-core-admin.js (94%) rename {admin/js => js}/vendor/picker.date.js (100%) rename {admin/js => js}/vendor/picker.js (100%) rename {admin/js => js}/vendor/powerange.min.js (100%) rename {admin/js => js}/vendor/switchery.min.js (100%) rename {admin/js => js}/vendor/tipr.min.js (100%) delete mode 100644 lib/Encryption/Encryption.php create mode 100644 lib/class-ccb-core-vendor-encryption.php diff --git a/README.txt b/README.txt index 5faf81f..0af1275 100644 --- a/README.txt +++ b/README.txt @@ -1,9 +1,9 @@ === Church Community Builder Core API === Contributors: jaredcobb Tags: ccb, church, api, chms -Requires at least: 3.0.1 -Tested up to: 4.3.1 -Stable tag: 0.9.6 +Requires at least: 4.4.0 +Tested up to: 4.7.2 +Stable tag: 1.0.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -14,7 +14,7 @@ Provides a core integration to the Church Community Builder API. Church Community Builder Core API *synchronizes* your church data to WordPress [custom post types](https://codex.wordpress.org/Custom_Post_Types). This plugin is geared toward developers (or advanced WordPress users who aren't afraid to get into a little bit of code). -Find out more at [http://www.wpccb.com/](http://www.wpccb.com). +Find out more at [https://www.wpccb.com](https://www.wpccb.com). = Why Use This Plugin? = @@ -34,7 +34,7 @@ your theme, widgets, or even your own plugins! = Documentation = -The [http://www.wpccb.com/documentation/](official documentation) has more information, including code samples, hooks, filters, and links to tutorials. +The [official documentation](https://www.wpccb.com/documentation) has more information, including code samples, hooks, filters, and links to tutorials. == Installation == @@ -66,6 +66,11 @@ allow the group to be publicly listed. A great way to cross reference if your gr == Changelog == += 1.0.0 = +* Official stable release +* Fixed broken group images (CCB API query parameter `include_image_link=true`) +* Refactored code to be faster, simpler, prettier, tastier + = 0.9.6 = * Added automatic flushing of rewrite rules when custom post type settings are changed * Added link to official documentation in README and About page @@ -89,7 +94,7 @@ allow the group to be publicly listed. A great way to cross reference if your gr = 0.9.2 = * Added tooltips to some settings to help explain the functionality * Added better defaults for date ranges -* Updated the plugin web site to http://www.wpccb.com +* Updated the plugin web site to https://www.wpccb.com = 0.9.1 = * Fixed an issue where some web hosts were not saving encypted passwords diff --git a/admin/class-ccb-core-admin.php b/admin/class-ccb-core-admin.php index d264ec7..6a394df 100644 --- a/admin/class-ccb-core-admin.php +++ b/admin/class-ccb-core-admin.php @@ -2,7 +2,7 @@ /** * The dashboard-specific functionality of the plugin. * - * @link http://jaredcobb.com/ccb-core + * @link https://www.wpccb.com * @since 0.9.0 * * @package CCB_Core @@ -27,69 +27,6 @@ public function __construct() { parent::__construct(); } - /** - * Initialize the Settings Menu and Page - * - * @access public - * @since 0.9.0 - * @return void - */ - public function initialize_settings_menu() { - - $settings = new CCB_Core_Settings(); - $settings_definitions = $settings->get_settings_definitions(); - $settings_page = new CCB_Core_Settings_Page( $this->plugin_settings_name ); - - add_menu_page( $this->plugin_display_name, $this->plugin_short_display_name, 'manage_options', $this->plugin_settings_name, '__return_null', 'dashicons-update', '80.9' ); - - if ( is_array( $settings_definitions ) && ! empty( $settings_definitions ) ) { - foreach ( $settings_definitions as $page_id => $page ) { - $settings_page = new CCB_Core_Settings_Page( $page_id, $page ); - add_submenu_page( $this->plugin_settings_name, $page['page_title'], $page['page_title'], 'manage_options', $page_id, array( $settings_page, 'render_page' ) ); - } - } - } - - /** - * Initialize the Settings - * - * @access public - * @since 0.9.0 - * @return void - */ - public function initialize_settings() { - - $settings = new CCB_Core_Settings(); - $settings_definitions = $settings->get_settings_definitions(); - - if ( is_array( $settings_definitions ) && ! empty( $settings_definitions ) ) { - foreach ( $settings_definitions as $page_id => $page ) { - - register_setting( $page_id, $this->plugin_settings_name, array( $settings, 'validate_settings' ) ); - - if ( isset( $page['sections'] ) && ! empty( $page['sections'] ) ) { - foreach ( $page['sections'] as $section_id => $section ) { - - $settings_section = new CCB_Core_Settings_Section( $section_id, $section ); - add_settings_section( $section_id, $section['section_title'], array( $settings_section, 'render_section' ), $page_id ); - - if ( isset( $section['fields'] ) && ! empty( $section['fields'] ) ) { - foreach ( $section['fields'] as $field_id => $field ) { - - $settings_field = new CCB_Core_Settings_Field( $field_id, $field ); - add_settings_field( $field_id, $field['field_title'], array( $settings_field, 'render_field' ), $page_id, $section_id ); - - } - } - - } - } - - } - } - - } - /** * Just before the settings are saved, check for changes * that would require us to flush the rewrite rules @@ -231,19 +168,6 @@ public function ajax_test_credentials() { } - /** - * Create a helpful settings link on the plugin page - * - * @param array $links - * @access public - * @since 0.9.0 - * @return array - */ - public function add_settings_link( $links ) { - $links[] = 'Settings'; - return $links; - } - /** * Check if we should schedule a synchronization based on * the options set by the user @@ -287,43 +211,4 @@ public function auto_sync() { $sync->sync(); } - /** - * Register the stylesheets for the dashboard. - * - * @since 0.9.0 - */ - public function enqueue_styles( $hook ) { - - if ( stristr( $hook, $this->plugin_settings_name ) !== false ) { - wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/ccb-core-admin.css', array(), $this->version, 'all' ); - wp_enqueue_style( 'switchery', plugin_dir_url( __FILE__ ) . 'css/vendor/switchery.min.css', array(), $this->version, 'all' ); - wp_enqueue_style( 'powerange', plugin_dir_url( __FILE__ ) . 'css/vendor/powerange.min.css', array(), $this->version, 'all' ); - wp_enqueue_style( 'picker', plugin_dir_url( __FILE__ ) . 'css/vendor/default.css', array(), $this->version, 'all' ); - wp_enqueue_style( 'picker-date', plugin_dir_url( __FILE__ ) . 'css/vendor/default.date.css', array(), $this->version, 'all' ); - wp_enqueue_style( 'tipr', plugin_dir_url( __FILE__ ) . 'css/vendor/tipr.css', array(), $this->version, 'all' ); - } - - } - - /** - * Register the scripts for the dashboard. - * - * @since 0.9.0 - */ - public function enqueue_scripts( $hook ) { - - if ( stristr( $hook, $this->plugin_settings_name ) !== false ) { - wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/ccb-core-admin.js', array( 'jquery' ), $this->version, false ); - wp_enqueue_script( 'switchery', plugin_dir_url( __FILE__ ) . 'js/vendor/switchery.min.js', array( 'jquery' ), $this->version, false ); - wp_enqueue_script( 'powerange', plugin_dir_url( __FILE__ ) . 'js/vendor/powerange.min.js', array( 'jquery' ), $this->version, false ); - wp_enqueue_script( 'picker', plugin_dir_url( __FILE__ ) . 'js/vendor/picker.js', array( 'jquery' ), $this->version, false ); - wp_enqueue_script( 'picker-date', plugin_dir_url( __FILE__ ) . 'js/vendor/picker.date.js', array( 'picker' ), $this->version, false ); - wp_enqueue_script( 'tipr', plugin_dir_url( __FILE__ ) . 'js/vendor/tipr.min.js', array( 'jquery' ), $this->version, false ); - wp_localize_script( $this->plugin_name, strtoupper( $this->plugin_settings_name ), array( - 'nextNonce' => wp_create_nonce( $this->plugin_name . '-nonce' )) - ); - } - - } - } diff --git a/admin/class-ccb-core-settings-field.php b/admin/class-ccb-core-settings-field.php deleted file mode 100644 index 37b25fe..0000000 --- a/admin/class-ccb-core-settings-field.php +++ /dev/null @@ -1,283 +0,0 @@ - - */ -class CCB_Core_Settings_Field extends CCB_Core_Plugin { - - /** - * The key for the field in the settings array - * - * @since 0.9.0 - * @access protected - * @var string $field_id - */ - protected $field_id; - - /** - * An array of field settings - * - * @since 0.9.0 - * @access protected - * @var array $field - */ - protected $field; - - /** - * The existing settings currently stored - * - * @since 0.9.0 - * @access protected - * @var array $existing_settings - */ - protected $existing_settings; - - /** - * Initialize the class and set its properties. - * - * @access public - * @since 0.9.0 - * @return void - */ - public function __construct( $field_id, $field ) { - - parent::__construct(); - - $this->field_id = $field_id; - $this->field = $field; - $this->existing_settings = get_option( $this->plugin_settings_name ); - - } - - /** - * General method that calls correct field render method based on config - * - * @access public - * @since 0.9.0 - * @return void - */ - public function render_field() { - if ( isset( $this->field['field_render_function'] ) && is_callable( array( $this, $this->field['field_render_function'] ) ) ) { - call_user_func( array( $this, $this->field['field_render_function'] ) ); - if ( isset( $this->field['field_tooltip'] ) ) { - echo ''; - } - } - } - - /** - * Render a textfield - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_text() { - $value = ''; - $attributes = $this->build_attributes_string(); - - if ( isset( $this->existing_settings[ $this->field_id ] ) ) { - $value = $this->existing_settings[ $this->field_id ]; - } - - echo "field['field_placeholder']}\" name=\"{$this->plugin_settings_name}[{$this->field_id}]\" value=\"{$value}\" {$attributes} />"; - } - - /** - * Render a switch button (checkbox) - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_switch() { - $value = ''; - $attributes = $this->build_attributes_string(); - - if ( isset( $this->existing_settings[ $this->field_id ] ) ) { - $value = $this->existing_settings[ $this->field_id ]; - } - - echo "plugin_settings_name}[{$this->field_id}]\" value=\"1\" " . checked( $value, '1', false ) . "{$attributes} />"; - } - - /** - * Render a slider widget (textfield) - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_slider() { - $value = $this->field['field_default']; - $attributes = $this->build_attributes_string(); - - if ( isset( $this->existing_settings[ $this->field_id ] ) ) { - $value = $this->existing_settings[ $this->field_id ]; - } - - echo "
plugin_settings_name}[{$this->field_id}]\" value=\"{$value}\"{$attributes} data-sibling=\"{$this->field_id}-readonly\" data-min=\"{$this->field['field_options']['min']}\" data-max=\"{$this->field['field_options']['max']}\" />
"; - echo ' ' . $this->field['field_options']['units'] . ''; - } - - /** - * Render a jQuery date picker - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_date_picker() { - $value = ''; - $attributes = $this->build_attributes_string(); - - if ( isset( $this->existing_settings[ $this->field_id ] ) ) { - $value = $this->existing_settings[ $this->field_id ]; - } - - echo "
plugin_settings_name}[{$this->field_id}]\" data-value=\"{$value}\" {$attributes} />
"; - } - - /** - * Render radio buttons - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_radio() { - - $value = $this->field['field_default']; - $attributes = $this->build_attributes_string(); - - if ( isset( $this->existing_settings[ $this->field_id ] ) ) { - $value = $this->existing_settings[ $this->field_id ]; - } - - echo '
'; - foreach ( (array) $this->field['field_options'] as $option_value => $option_label ) { - echo "
"; - } - echo '
'; - - } - - /** - * Render a username and password field - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_credentials() { - $value = array(); - if ( isset( $this->existing_settings[ $this->field_id ] ) ) { - $value['username'] = $this->existing_settings[ $this->field_id ]['username']; - $value['password'] = $this->decrypt( $this->existing_settings[ $this->field_id ]['password'] ); - } - else { - $value['username'] = ''; - $value['password'] = ''; - } - echo << - -HTML; - } - - /** - * Render a test credentials button - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_test_credentials() { - if ( ! isset( $this->existing_settings['credentials']['username'] ) || empty( $this->existing_settings['credentials']['username'] ) || ! isset( $this->existing_settings['credentials']['password'] ) || empty( $this->existing_settings['credentials']['password'] ) ) { - echo '

Please enter your API Credentials

'; - } - elseif ( ! isset( $this->existing_settings['subdomain'] ) || empty( $this->existing_settings['subdomain'] ) ) { - echo '

Please enter your Church Community Builder subdomain.

'; - } - else { - echo << - -
- -HTML; - } - } - - /** - * Render a manual sync button - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_manual_sync() { - $sync_in_progress = get_transient( $this->plugin_name . '-sync-in-progress' ); - $sync_message = ''; - $button_disabled = ''; - $spinner_active = ''; - - if ( $sync_in_progress ) { - $sync_message = '
Syncronization in progress... You can safely navigate away from this page while we work hard in the background. (It should be just a moment).
'; - $button_disabled = 'disabled'; - $spinner_active = 'is-active'; - } - echo << - -
- {$sync_message} - -HTML; - } - - /** - * Render an area to display the latest sync results - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function render_latest_results() { - echo << -
- -HTML; - } - - /** - * Helper method to build HTML attributes from the config - * - * @access protected - * @since 0.9.0 - * @return string - */ - protected function build_attributes_string() { - $attributes = ''; - if ( isset( $this->field['field_attributes'] ) && ! empty( $this->field['field_attributes'] ) ) { - foreach ( $this->field['field_attributes'] as $attr_name => $attr_value ) { - $attributes .= " {$attr_name}='{$attr_value}'"; - } - } - return $attributes; - } -} diff --git a/ccb-core.php b/ccb-core.php index 30470b6..9cc25c6 100644 --- a/ccb-core.php +++ b/ccb-core.php @@ -2,48 +2,48 @@ /** * Church Community Builder Core API * - * @link http://www.wpccb.com + * @link https://www.wpccb.com * @since 0.9.0 * @package CCB_Core * * @wordpress-plugin * Plugin Name: Church Community Builder Core API - * Plugin URI: http://www.wpccb.com + * Plugin URI: https://www.wpccb.com * Description: A plugin to provide a core integration of the Church Community Builder API into WordPress custom post types - * Version: 0.9.6 + * Version: 1.0.0 * Author: Jared Cobb - * Author URI: http://jaredcobb.com/ + * Author URI: https://www.jaredcobb.com/ * License: GPL-2.0+ * License URI: http://www.gnu.org/licenses/gpl-2.0.txt * Text Domain: ccb-core * Domain Path: /languages */ -// do not allow direct access to this file +// do not allow direct access to this file. if ( ! defined( 'WPINC' ) ) { die; } -// parent class for entire plugin (name, version, other helpful properties and utility methods) -require_once plugin_dir_path( __FILE__ ) . 'includes/class-ccb-core-plugin.php'; +define( 'CCB_CORE_PATH', plugin_dir_path( __FILE__ ) ); +define( 'CCB_CORE_URL', plugin_dir_url( __FILE__ ) ); +define( 'CCB_CORE_BASENAME', plugin_basename( __FILE__ ) ); +define( 'CCB_CORE_VERSION', '1.0.0' ); -// code that runs during plugin activation -require_once plugin_dir_path( __FILE__ ) . 'includes/class-ccb-core-activator.php'; +// code that runs during plugin activation. +require_once CCB_CORE_PATH . 'includes/class-ccb-core-activator.php'; register_activation_hook( __FILE__, array( 'CCB_Core_Activator', 'activate' ) ); // internationalization, dashboard-specific hooks, and public-facing site hooks. -require_once plugin_dir_path( __FILE__ ) . 'includes/class-ccb-core.php'; +require_once CCB_CORE_PATH . 'includes/class-ccb-core.php'; /** * Begin execution of the plugin. * - * @since 0.9.0 + * @access public + * @return void */ function run_ccb_core() { - - $plugin_basename = plugin_basename( __FILE__ ); - $plugin = new CCB_Core( $plugin_basename ); - $plugin->run(); + $plugin = new CCB_Core(); } run_ccb_core(); diff --git a/admin/css/ccb-core-admin.css b/css/ccb-core-admin.css similarity index 100% rename from admin/css/ccb-core-admin.css rename to css/ccb-core-admin.css diff --git a/admin/css/vendor/default.css b/css/vendor/default.css similarity index 100% rename from admin/css/vendor/default.css rename to css/vendor/default.css diff --git a/admin/css/vendor/default.date.css b/css/vendor/default.date.css similarity index 100% rename from admin/css/vendor/default.date.css rename to css/vendor/default.date.css diff --git a/admin/css/vendor/powerange.min.css b/css/vendor/powerange.min.css similarity index 100% rename from admin/css/vendor/powerange.min.css rename to css/vendor/powerange.min.css diff --git a/admin/css/vendor/switchery.min.css b/css/vendor/switchery.min.css similarity index 100% rename from admin/css/vendor/switchery.min.css rename to css/vendor/switchery.min.css diff --git a/admin/css/vendor/tipr.css b/css/vendor/tipr.css similarity index 100% rename from admin/css/vendor/tipr.css rename to css/vendor/tipr.css diff --git a/includes/class-ccb-core-activator.php b/includes/class-ccb-core-activator.php index 9677cd6..97e4607 100644 --- a/includes/class-ccb-core-activator.php +++ b/includes/class-ccb-core-activator.php @@ -2,7 +2,7 @@ /** * Fired during plugin activation * - * @link http://jaredcobb.com/ccb-core + * @link https://www.wpccb.com * @since 0.9.0 * * @package CCB_Core @@ -19,7 +19,7 @@ * @subpackage CCB_Core/includes * @author Jared Cobb */ -class CCB_Core_Activator extends CCB_Core_Plugin { +class CCB_Core_Activator { /** * Activation code @@ -27,7 +27,7 @@ class CCB_Core_Activator extends CCB_Core_Plugin { * @since 0.9.0 */ public static function activate() { - // TODO: check dependencies like mcrypt and memory limits + // TODO: check dependencies like mcrypt and memory limits. } } diff --git a/includes/class-ccb-core-helpers.php b/includes/class-ccb-core-helpers.php new file mode 100644 index 0000000..5018209 --- /dev/null +++ b/includes/class-ccb-core-helpers.php @@ -0,0 +1,138 @@ + + */ +class CCB_Core_Helpers { + + const SYNC_STATUS_KEY = 'ccb-core-sync-in-progress'; + + /** + * Instance of the Helper class + * + * @var CCB_Core_Helpers + * @access protected + * @static + */ + private static $instance; + + /** + * The options set by the user + * + * @var array + */ + private $plugin_options = array(); + + /** + * Unused constructor in the singleton pattern + * + * @access public + * @return void + */ + public function __construct() { + // Initialize this class with the instance() method. + } + + /** + * Returns the instance of the class + * + * @access public + * @static + * @return CCB_Core_Helpers + */ + public static function instance() { + if ( ! isset( static::$instance ) ) { + static::$instance = new CCB_Core_Helpers(); + static::$instance->setup(); + } + return static::$instance; + } + + /** + * Initial setup of the singleton + * + * @access private + * @return void + */ + private function setup() { + // Get any options the user may have set. + $this->plugin_options = get_option( 'ccb_core' ); + } + + /** + * Get any options stored by the user + * + * @return array + */ + public function get_options() { + return $this->plugin_options; + } + + /** + * Encrypts and base64_encodes a string safe for serialization in WordPress + * + * @since 1.0.0 + * @access public + * @param string $data The data to be encrypted. + * @return string + */ + public function encrypt( $data ) { + + $encrypted_value = false; + $key = wp_salt() . md5( 'ccb-core' ); + + if ( ! empty( $data ) ) { + try { + $e = new CCB_Core_Vendor_Encryption( MCRYPT_BlOWFISH, MCRYPT_MODE_CBC ); + $encrypted_value = base64_encode( $e->encrypt( $data, $key ) ); + } catch ( Exception $ex ) { + // TODO: Better exception handling. + } + + } + + return $encrypted_value; + } + + /** + * Decrypts and base64_decodes a string + * + * @since 1.0.0 + * @access public + * @param string $data The data to be decrypted. + * @return string + */ + public function decrypt( $data ) { + + $decrypted_value = false; + $key = wp_salt() . md5( 'ccb-core' ); + + if ( ! empty( $data ) ) { + try { + $e = new CCB_Core_Vendor_Encryption( MCRYPT_BlOWFISH, MCRYPT_MODE_CBC ); + $decrypted_value = $e->decrypt( base64_decode( $data ), $key ); + } catch ( Exception $ex ) { + // TODO: Better exception handling. + } + + } + + return $decrypted_value; + } + +} diff --git a/includes/class-ccb-core-i18n.php b/includes/class-ccb-core-i18n.php deleted file mode 100644 index 3e45f32..0000000 --- a/includes/class-ccb-core-i18n.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ -class CCB_Core_i18n extends CCB_Core_Plugin { - - /** - * Load the plugin text domain for translation. - * - * @since 0.9.0 - */ - public function load_plugin_textdomain() { - - load_plugin_textdomain( - $this->plugin_name, - false, - dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/' - ); - - } - -} diff --git a/includes/class-ccb-core-loader.php b/includes/class-ccb-core-loader.php deleted file mode 100644 index 09bea74..0000000 --- a/includes/class-ccb-core-loader.php +++ /dev/null @@ -1,128 +0,0 @@ - - */ -class CCB_Core_Loader extends CCB_Core_Plugin { - - /** - * The array of actions registered with WordPress. - * - * @since 0.9.0 - * @access protected - * @var array $actions The actions registered with WordPress to fire when the plugin loads. - */ - protected $actions; - - /** - * The array of filters registered with WordPress. - * - * @since 0.9.0 - * @access protected - * @var array $filters The filters registered with WordPress to fire when the plugin loads. - */ - protected $filters; - - /** - * Initialize the collections used to maintain the actions and filters. - * - * @since 0.9.0 - */ - public function __construct() { - - $this->actions = array(); - $this->filters = array(); - - } - - /** - * Add a new action to the collection to be registered with WordPress. - * - * @since 0.9.0 - * @var string $hook The name of the WordPress action that is being registered. - * @var object $component A reference to the instance of the object on which the action is defined. - * @var string $callback The name of the function definition on the $component. - * @var int Optional $priority The priority at which the function should be fired. - * @var int Optional $accepted_args The number of arguments that should be passed to the $callback. - */ - public function add_action( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) { - $this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args ); - } - - /** - * Add a new filter to the collection to be registered with WordPress. - * - * @since 0.9.0 - * @var string $hook The name of the WordPress filter that is being registered. - * @var object $component A reference to the instance of the object on which the filter is defined. - * @var string $callback The name of the function definition on the $component. - * @var int Optional $priority The priority at which the function should be fired. - * @var int Optional $accepted_args The number of arguments that should be passed to the $callback. - */ - public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) { - $this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args ); - } - - /** - * A utility function that is used to register the actions and hooks into a single - * collection. - * - * @since 0.9.0 - * @access private - * @var array $hooks The collection of hooks that is being registered (that is, actions or filters). - * @var string $hook The name of the WordPress filter that is being registered. - * @var object $component A reference to the instance of the object on which the filter is defined. - * @var string $callback The name of the function definition on the $component. - * @var int Optional $priority The priority at which the function should be fired. - * @var int Optional $accepted_args The number of arguments that should be passed to the $callback. - * @return type The collection of actions and filters registered with WordPress. - */ - private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) { - - $hooks[] = array( - 'hook' => $hook, - 'component' => $component, - 'callback' => $callback, - 'priority' => $priority, - 'accepted_args' => $accepted_args - ); - - return $hooks; - - } - - /** - * Register the filters and actions with WordPress. - * - * @since 0.9.0 - */ - public function run() { - - foreach ( $this->filters as $hook ) { - add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] ); - } - - foreach ( $this->actions as $hook ) { - add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] ); - } - - } - -} diff --git a/includes/class-ccb-core-plugin.php b/includes/class-ccb-core-plugin.php index cce6326..9fed1da 100644 --- a/includes/class-ccb-core-plugin.php +++ b/includes/class-ccb-core-plugin.php @@ -2,7 +2,7 @@ /** * Parent class for all plugin files * - * @link http://jaredcobb.com/ccb-core + * @link https://www.wpccb.com * @since 0.9.0 * * @package CCB_Core @@ -83,60 +83,6 @@ public function __construct() { } - /** - * Encrypts and base64_encodes a string safe for serialization in WordPress - * - * @since 0.9.0 - * @access protected - * @param string $data - * @return string - */ - protected function encrypt( $data ) { - - $encrypted_value = false; - $key = wp_salt() . md5( $this->plugin_name ); - - if ( ! empty( $data ) ) { - try { - $e = new CCB_Core_Vendor_Encryption( MCRYPT_BlOWFISH, MCRYPT_MODE_CBC ); - $encrypted_value = base64_encode( $e->encrypt( $data, $key ) ); - } - catch ( Exception $ex ) { - // TODO: Better exception handling - } - - } - - return $encrypted_value; - } - - /** - * Decrypts and base64_decodes a string - * - * @since 0.9.0 - * @access protected - * @param string $data - * @return string - */ - protected function decrypt( $data ) { - - $decrypted_value = false; - $key = wp_salt() . md5( $this->plugin_name ); - - if ( ! empty( $data ) ) { - try { - $e = new CCB_Core_Vendor_Encryption( MCRYPT_BlOWFISH, MCRYPT_MODE_CBC ); - $decrypted_value = $e->decrypt( base64_decode( $data ), $key ); - } - catch ( Exception $ex ) { - // TODO: Better exception handling - } - - } - - return $decrypted_value; - } - /** * Responds to the client with a json response * but allows the script to continue @@ -169,7 +115,7 @@ protected function send_non_blocking_json_response( $response ) { /** * Helper function to check if a date is valid * - * @param string $date + * @param string $date The date. * @param string $format * @access protected * @since 0.9.0 diff --git a/includes/class-ccb-core-settings-field.php b/includes/class-ccb-core-settings-field.php new file mode 100644 index 0000000..d45a0ac --- /dev/null +++ b/includes/class-ccb-core-settings-field.php @@ -0,0 +1,347 @@ + + */ +class CCB_Core_Settings_Field { + + /** + * The key for the field in the settings array + * + * @since 0.9.0 + * @access protected + * @var string $field_id + */ + protected $field_id; + + /** + * An array of field settings + * + * @since 0.9.0 + * @access protected + * @var array $field + */ + protected $field; + + /** + * The existing settings currently stored + * + * @since 0.9.0 + * @access protected + * @var array $existing_settings + */ + protected $existing_settings; + + /** + * Initialize the class and set its properties. + * + * @param string $field_id Slug of the field. + * @param array $field Array of field settings. + * @return void + */ + public function __construct( $field_id, $field ) { + + $this->field_id = $field_id; + $this->field = $field; + $this->existing_settings = CCB_Core_Helpers::instance()->get_options(); + + } + + /** + * General method that calls correct field render method based on config + * + * @access public + * @since 0.9.0 + * @return void + */ + public function render_field() { + if ( isset( $this->field['field_render_function'] ) && is_callable( array( $this, $this->field['field_render_function'] ) ) ) { + call_user_func( array( $this, $this->field['field_render_function'] ) ); + if ( isset( $this->field['field_tooltip'] ) ) { + echo ''; + } + } + } + + /** + * Render a textfield + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_text() { + $value = ''; + + if ( isset( $this->existing_settings[ $this->field_id ] ) ) { + $value = $this->existing_settings[ $this->field_id ]; + } + + echo sprintf( + 'field['field_placeholder'] ), + esc_attr( $this->field_id ), + esc_attr( $value ) + ); + + $this->output_attributes(); + + echo '/>'; + } + + /** + * Render a switch button (checkbox) + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_switch() { + $value = ''; + + if ( isset( $this->existing_settings[ $this->field_id ] ) ) { + $value = $this->existing_settings[ $this->field_id ]; + } + + echo sprintf( + 'field_id ), + checked( $value, '1', false ) + ); + + $this->output_attributes(); + + echo '/>'; + } + + /** + * Render a slider widget (textfield) + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_slider() { + $value = $this->field['field_default']; + + if ( isset( $this->existing_settings[ $this->field_id ] ) ) { + $value = $this->existing_settings[ $this->field_id ]; + } + + echo sprintf( + '
+ field_id ), + esc_attr( $value ), + esc_attr( $this->field['field_options']['min'] ), + esc_attr( $this->field['field_options']['max'] ) + ); + + $this->output_attributes(); + + echo ' />
'; + + echo sprintf( + '%2$s', + esc_attr( $this->field_id ), + esc_html( $this->field['field_options']['units'] ) + ); + } + + /** + * Render a jQuery date picker + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_date_picker() { + $value = ''; + + if ( isset( $this->existing_settings[ $this->field_id ] ) ) { + $value = $this->existing_settings[ $this->field_id ]; + } + + echo sprintf( + '
+ field_id ), + esc_attr( $value ) + ); + + $this->output_attributes(); + + echo '/>
'; + } + + /** + * Render radio buttons + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_radio() { + + $value = $this->field['field_default']; + + if ( isset( $this->existing_settings[ $this->field_id ] ) ) { + $value = $this->existing_settings[ $this->field_id ]; + } + + echo '
'; + foreach ( (array) $this->field['field_options'] as $option_value => $option_label ) { + echo sprintf( + '
', + esc_attr( $option_label ) + ); + } + echo '
'; + + } + + /** + * Render a username and password field + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_credentials() { + $value = array(); + if ( isset( $this->existing_settings[ $this->field_id ] ) ) { + $value['username'] = $this->existing_settings[ $this->field_id ]['username']; + $value['password'] = CCB_Core_Helpers::instance()->decrypt( $this->existing_settings[ $this->field_id ]['password'] ); + } else { + $value['username'] = ''; + $value['password'] = ''; + } + + echo sprintf( + '', + esc_attr__( 'Username', 'ccb-core' ), + esc_attr( $this->field_id ), + esc_attr( $value['username'] ) + ); + echo sprintf( + '', + esc_attr__( 'Password', 'ccb-core' ), + esc_attr( $this->field_id ), + esc_attr( $value['password'] ) + ); + } + + /** + * Render a test credentials button + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_test_credentials() { + if ( empty( $this->existing_settings['credentials']['username'] ) || empty( $this->existing_settings['credentials']['password'] ) ) { + echo sprintf( + '

%s

', + esc_html__( 'Please enter your API Credentials' ) + ); + } elseif ( empty( $this->existing_settings['subdomain'] ) ) { + echo sprintf( + '

%s

', + esc_html__( 'Please enter your Church Community Builder subdomain.' ) + ); + } else { + echo sprintf( + '', + esc_attr__( 'Test Credentials', 'ccb-core' ) + ); + } + } + + /** + * Render a manual sync button + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_manual_sync() { + $sync_in_progress = get_transient( CCB_Core_Helpers::SYNC_STATUS_KEY ); + + $sync_message = $sync_in_progress ? __( 'Syncronization in progress... You can safely navigate away from this page while we work hard in the background. (It should be just a moment).' ) : ''; + $button_disabled = $sync_in_progress ? 'disabled' : ''; + $spinner_active = $sync_in_progress ? 'is-active' :''; + + echo sprintf( + '
+ +
+
%4$s
+
', + esc_attr( $button_disabled ), + esc_attr__( 'Synchronize', 'ccb-core' ), + esc_attr( $spinner_active ), + esc_html( $sync_message ) + ); + } + + /** + * Render an area to display the latest sync results + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function render_latest_results() { + echo '
+
+
'; + } + + /** + * Helper method to echo HTML attributes from the config + * + * @access protected + * @since 0.9.0 + * @return void + */ + protected function output_attributes() { + $attributes = ''; + if ( ! empty( $this->field['field_attributes'] ) ) { + foreach ( $this->field['field_attributes'] as $attr_name => $attr_value ) { + echo sprintf( + '%1$s="%2$s" ', + esc_attr( $attr_name ), + esc_attr( $attr_value ) + ); + } + } + } +} diff --git a/admin/class-ccb-core-settings-page.php b/includes/class-ccb-core-settings-page.php similarity index 65% rename from admin/class-ccb-core-settings-page.php rename to includes/class-ccb-core-settings-page.php index 391625d..3d7c01b 100644 --- a/admin/class-ccb-core-settings-page.php +++ b/includes/class-ccb-core-settings-page.php @@ -2,7 +2,7 @@ /** * Everything related to the plugin settings pages * - * @link http://jaredcobb.com/ccb-core + * @link https://www.wpccb.com * @since 0.9.0 * * @package CCB_Core @@ -16,7 +16,7 @@ * @subpackage CCB_Core/admin * @author Jared Cobb */ -class CCB_Core_Settings_Page extends CCB_Core_Plugin { +class CCB_Core_Settings_Page { /** * The key for the page in the settings array @@ -30,16 +30,11 @@ class CCB_Core_Settings_Page extends CCB_Core_Plugin { /** * Initialize the class and set its properties. * - * @access public - * @since 0.9.0 - * @return void + * @param string $page_id The slug of the page. + * @return void */ public function __construct( $page_id ) { - - parent::__construct(); - $this->page_id = $page_id; - } /** @@ -50,13 +45,13 @@ public function __construct( $page_id ) { * @return void */ public function render_page() { - if ( ! current_user_can( 'manage_options' ) ) { - wp_die( __( 'You do not have sufficient permissions to access this page.', $this->plugin_name ) ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'ccb-core' ) ); } ?> -
-

plugin_display_name; ?>

+
+

@@ -64,10 +59,10 @@ public function render_page() { page_id ); ?> page_id != 'ccb_core_settings' ) { + if ( 'ccb_core_settings' !== $this->page_id ) { ?>

- +

*/ -class CCB_Core_Settings_Section extends CCB_Core_Plugin { +class CCB_Core_Settings_Section { /** * The key for the section in the settings array @@ -39,17 +39,13 @@ class CCB_Core_Settings_Section extends CCB_Core_Plugin { /** * Initialize the class and set its properties. * - * @access public - * @since 0.9.0 - * @return void + * @param string $section_id The slug of the section. + * @param array $section An array of section settings. + * @return void */ public function __construct( $section_id, $section ) { - - parent::__construct(); - $this->section_id = $section_id; $this->section = $section; - } /** @@ -64,24 +60,15 @@ public function __construct( $section_id, $section ) { */ public function render_section_about() { - // if the user has set their subdomain, use it for the url to w_group_list.php - $settings = get_option( $this->plugin_settings_name ); - if ( isset( $settings['subdomain'] ) && ! empty( $settings['subdomain'] ) ) { - $w_group_list = "https://{$settings['subdomain']}.ccbchurch.com/w_group_list.php"; - } - else { - $w_group_list = 'https://[yoursite].ccbchurch.com/w_group_list.php'; - } - - // this unfortunately includes a dirty hack to prevent chrome from autopopulating username/password - // so this will inject a fake login panel because chrome ignores autocomplete="off" - echo <<

Church Community Builder Core API synchronizes your church data to WordPress custom post types. - This plugin is geared toward developers (or advanced WordPress users who aren't afraid to get into a little bit of code). + This plugin is geared toward developers (or advanced WordPress users who aren\'t afraid to get into a little bit of code).

Why Use This Plugin?

@@ -121,7 +108,7 @@ public function render_section_about() {

Frequently Asked Questions

- I installed this plugin and my site doesn't look any different + I installed this plugin and my site doesn\'t look any different

@@ -132,27 +119,41 @@ public function render_section_about() {

- Some of my groups in Church Community Builder aren't being synchronized + Some of my groups in Church Community Builder aren\'t being synchronized -

+

'; + echo '
- You'll need to ensure your group settings - allow the group to be publicly listed. A great way to cross reference if your group is publicly visible is to visit - {$w_group_list} and see if the missing group shows up there. + You\'ll need to ensure your group settings + allow the group to be publicly listed. A great way to cross reference if your group is publicly visible is to visit '; + + // If the user has set their subdomain, use it for the url to w_group_list.php. + $options = CCB_Core_Helpers::instance()->get_options(); + if ( ! empty( $options['subdomain'] ) ) { + echo sprintf( + ' + %1$s + ', + esc_url( $options['subdomain'] ) + ); + } else { + echo 'https://[yoursite].ccbchurch.com/w_group_list.php'; + } + + echo ' and see if the missing group shows up there.

Documentation

- The official documentation has more information, including code samples, hooks & filters, and links to tutorials. + The official documentation has more information, including code samples, hooks & filters, and links to tutorials.

Support

Support is limited, but if you have questions as a user of the plugin you can submit them on the official WordPress plugin support forum. - If you're a Developer and would like to submit a bug report or pull request, you can do that on GitHub. -

-HTML; + If you\'re a Developer and would like to submit a bug report or pull request, you can do that on GitHub. +

'; } /** @@ -163,8 +164,8 @@ public function render_section_about() { * @return void */ public function render_section() { - if ( $this->section_id == 'about' ) { - echo $this->render_section_about(); + if ( 'about' === $this->section_id ) { + $this->render_section_about(); } } diff --git a/admin/class-ccb-core-settings.php b/includes/class-ccb-core-settings.php similarity index 59% rename from admin/class-ccb-core-settings.php rename to includes/class-ccb-core-settings.php index b8cd58a..af455a2 100644 --- a/admin/class-ccb-core-settings.php +++ b/includes/class-ccb-core-settings.php @@ -2,7 +2,7 @@ /** * Everything related to the plugin settings * - * @link http://jaredcobb.com/ccb-core + * @link https://www.wpccb.com * @since 0.9.0 * * @package CCB_Core @@ -16,138 +16,122 @@ * @subpackage CCB_Core/admin * @author Jared Cobb */ -class CCB_Core_Settings extends CCB_Core_Plugin { - - /** - * Initialize the class and set its properties. - * - * @access public - * @since 0.9.0 - * @return void - */ - public function __construct() { - - parent::__construct(); - - } +class CCB_Core_Settings { /** * Validate the settings fields based on the settings config * - * @access public - * @since 0.9.0 - * @param array $input - * @return array $current_settings + * @access public + * @since 0.9.0 + * @param array $input An array of fields to sanitize. + * @return array $current_options */ public function validate_settings( $input ) { - $current_settings = get_option( $this->plugin_settings_name ); + $current_options = CCB_Core_Helpers::instance()->get_options(); $validation_hash = $this->generate_validation_hash(); - if ( is_array( $validation_hash ) && ! empty( $validation_hash ) ) { + foreach ( $validation_hash as $field_id => $validation ) { - foreach ( $validation_hash as $field_id => $validation ) { + if ( isset( $validation['field_validation'] ) ) { - if ( isset( $validation['field_validation'] ) ) { - switch ( $validation['field_validation'] ) { + switch ( $validation['field_validation'] ) { - case 'alphanumeric': - if ( empty( $input[ $field_id ] ) || ctype_alnum( $input[ $field_id ] ) ) { - $current_settings[ $field_id ] = $input[ $field_id ]; - } - else { - add_settings_error( $field_id, $field_id, "Oops! {$validation['field_title']} can only contain letters and numbers." ); - } - break; + case 'alphanumeric': - case 'numeric': - if ( empty( $input[ $field_id ] ) || ctype_digit( $input[ $field_id ] ) ) { - $current_settings[ $field_id ] = $input[ $field_id ]; - } - else { - add_settings_error( $field_id, $field_id, "Oops! {$validation['field_title']} can only contain numbers." ); - } - break; + if ( empty( $input[ $field_id ] ) || ctype_alnum( $input[ $field_id ] ) ) { + $current_options[ $field_id ] = $input[ $field_id ]; + } else { + add_settings_error( + $field_id, + $field_id, + sprintf( + esc_html__( + 'Oops! %s can only contain letters and numbers', + 'ccb-core' + ), + esc_html( $validation['field_title'] ) + ) + ); + } + break; - case 'slug': - $input[ $field_id ] = strtolower( str_replace( ' ', '_', $input[ $field_id ] ) ); - // continue onto alphanumeric_extended validation + case 'numeric': - case 'alphanumeric_extended': - if ( empty( $input[ $field_id ] ) || ! preg_match( '/[^\w\s-_]/', $input[ $field_id ] ) ) { - $current_settings[ $field_id ] = $input[ $field_id ]; - } - else { - add_settings_error( $field_id, $field_id, "Oops! {$validation['field_title']} can only contain letters, numbers, spaces, dashes, or underscores." ); - } - break; + if ( empty( $input[ $field_id ] ) || ctype_digit( $input[ $field_id ] ) ) { + $current_options[ $field_id ] = $input[ $field_id ]; + } else { + add_settings_error( + $field_id, + $field_id, + sprintf( + esc_html__( + 'Oops! %s can only contain numbers', + 'ccb-core' + ), + esc_html( $validation['field_title'] ) + ) + ); + } + break; - case 'encrypt': + case 'slug': - if ( ! empty( $input[ $field_id ]['password'] ) ) { - $encrypted_password = $this->encrypt( $input[ $field_id ]['password'] ); - if ( $encrypted_password ) { - $current_settings[ $field_id ]['password'] = $encrypted_password; - } - else { - add_settings_error( $field_id, $field_id, "Oops! We couldn't encrypt your password." ); - } - } - $current_settings[ $field_id ]['username'] = $input[ $field_id ]['username']; + // Continue onto alphanumeric_extended validation. + $input[ $field_id ] = sanitize_key( $input[ $field_id ] ); - break; + case 'alphanumeric_extended': - case 'switch': + if ( empty( $input[ $field_id ] ) || ! preg_match( '/[^\w\s-_]/', $input[ $field_id ] ) ) { + $current_options[ $field_id ] = $input[ $field_id ]; + } else { + add_settings_error( + $field_id, + $field_id, + sprintf( + esc_html__( + 'Oops! %s can only contain letters, numbers, spaces, dashes, or underscores.', + 'ccb-core' + ), + esc_html( $validation['field_title'] ) + ) + ); + } + break; - $current_settings[ $field_id ] = ( isset( $input[ $field_id ] ) && $input[ $field_id ] == '1' ? '1' : '' ); - break; + case 'encrypt': - default: - $current_settings[ $field_id ] = $input[ $field_id ]; - break; + if ( ! empty( $input[ $field_id ]['password'] ) ) { + $encrypted_password = CCB_Core_Helpers::instance()->encrypt( $input[ $field_id ]['password'] ); + if ( $encrypted_password ) { + $current_options[ $field_id ]['password'] = $encrypted_password; + } else { + add_settings_error( + $field_id, + $field_id, + 'Oops! We couldn\'t encrypt your password.' + ); + } + } + $current_options[ $field_id ]['username'] = $input[ $field_id ]['username']; - } - } + break; - } + case 'switch': - } + $current_options[ $field_id ] = ( isset( $input[ $field_id ] ) && '1' === $input[ $field_id ] ? '1' : '' ); + break; - return $current_settings; - } + default: + $current_options[ $field_id ] = $input[ $field_id ]; + break; - /** - * Helper function to create a name/value hash for quick validation - * - * @access protected - * @since 0.9.0 - * @return array $mapping - */ - protected function generate_validation_hash() { - $mapping = array(); - $page_id = $_POST['option_page']; - $settings_definitions = $this->get_settings_definitions(); - - if ( is_array( $settings_definitions ) && isset( $settings_definitions[ $page_id ] ) ) { - if ( isset( $settings_definitions[ $page_id ]['sections'] ) && ! empty( $settings_definitions[ $page_id ]['sections'] ) ) { - foreach ( $settings_definitions[ $page_id ]['sections'] as $section ) { - if ( isset( $section['fields'] ) && ! empty( $section['fields'] ) ) { - foreach ( $section['fields'] as $field_id => $field ) { - if ( isset( $field['field_validation'] ) ) { - $mapping[ $field_id ] = array( - 'field_title' => $field['field_title'], - 'field_validation' => $field['field_validation'], - ); - } - else { - $mapping[ $field_id ] = false; - } - } - } } } + } - return $mapping; + + return $current_options; } /** @@ -159,63 +143,63 @@ protected function generate_validation_hash() { */ public function get_settings_definitions() { return array( - $this->plugin_settings_name => array( - 'page_title' => 'About', + 'ccb_core_settings' => array( + 'page_title' => esc_html__( 'About', 'ccb-core' ), 'sections' => array( 'about' => array( - 'section_title' => 'About', - // no fields needed for the about page + 'section_title' => esc_html__( 'About', 'ccb-core' ), + // No fields needed for the about page. ), ), ), - $this->plugin_settings_name . '_api_settings' => array( - 'page_title' => 'API Settings', + 'ccb_core_settings_api_settings' => array( + 'page_title' => esc_html__( 'API Settings', 'ccb-core' ), 'sections' => array( 'api_settings' => array( - 'section_title' => 'API Settings', + 'section_title' => esc_html__( 'API Settings', 'ccb-core' ), 'fields' => array( 'subdomain' => array( - 'field_title' => 'Software Subdomain', + 'field_title' => esc_html__( 'Software Subdomain', 'ccb-core' ), 'field_render_function' => 'render_text', 'field_placeholder' => 'subdomain', 'field_validation' => 'alphanumeric', 'field_tooltip' => 'We just need the first part of your software URL (without "http://" and without ".ccbchurch.com").', ), 'credentials' => array( - 'field_title' => 'API Credentials', + 'field_title' => esc_html__( 'API Credentials', 'ccb-core' ), 'field_render_function' => 'render_credentials', 'field_validation' => 'encrypt', 'field_tooltip' => 'This is the username and password for the API user in your Church Community Builder software.', ), 'test_credentials' => array( - 'field_title' => 'Test Credentials', + 'field_title' => esc_html__( 'Test Credentials', 'ccb-core' ), 'field_render_function' => 'render_test_credentials', ), ), ), ), ), - $this->plugin_settings_name . '_groups' => array( - 'page_title' => 'Groups', + 'ccb_core_settings_groups' => array( + 'page_title' => esc_html__( 'Groups', 'ccb-core' ), 'sections' => array( 'groups' => array( - 'section_title' => 'Groups', + 'section_title' => esc_html__( 'Groups', 'ccb-core' ), 'fields' => array( 'groups-enabled' => array( - 'field_title' => 'Enable Groups', + 'field_title' => esc_html__( 'Enable Groups', 'ccb-core' ), 'field_render_function' => 'render_switch', 'field_validation' => 'switch', ), 'groups-name' => array( - 'field_title' => 'Groups Display Name', + 'field_title' => esc_html__( 'Groups Display Name', 'ccb-core' ), 'field_render_function' => 'render_text', - 'field_placeholder' => 'Groups', + 'field_placeholder' => esc_html__( 'Groups', 'ccb-core' ), 'field_validation' => 'alphanumeric_extended', 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1}' ), 'field_tooltip' => 'This is what you call the groups in your church (i.e. Home Groups, Connections, Life Groups, etc.).', ), 'groups-slug' => array( - 'field_title' => 'Groups URL Name', + 'field_title' => esc_html__( 'Groups URL Name', 'ccb-core' ), 'field_render_function' => 'render_text', 'field_placeholder' => 'groups', 'field_validation' => 'slug', @@ -223,62 +207,62 @@ public function get_settings_definitions() { 'field_tooltip' => 'This is typically where your theme will display all the groups. WordPress calls this a "slug".', ), 'groups-import-images' => array( - 'field_title' => 'Also Import Group Images?', + 'field_title' => esc_html__( 'Also Import Group Images?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'no', 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1}' ), - 'field_tooltip' => "This will download the CCB Group Image and attach it as a Featured Image.
If you don't need group images, then disabling this feature will speed up the synchronization.", + 'field_tooltip' => 'This will download the CCB Group Image and attach it as a Featured Image.
If you don\'t need group images, then disabling this feature will speed up the synchronization.', ), 'groups-advanced' => array( - 'field_title' => 'Enable Advanced Settings (Optional)', + 'field_title' => esc_html__( 'Enable Advanced Settings (Optional)', 'ccb-core' ), 'field_render_function' => 'render_switch', 'field_validation' => 'switch', 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1}' ), ), 'groups-exclude-from-search' => array( - 'field_title' => 'Exclude From Search?', + 'field_title' => esc_html__( 'Exclude From Search?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'no', 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1,"groups-advanced":1}' ), ), 'groups-publicly-queryable' => array( - 'field_title' => 'Publicly Queryable?', + 'field_title' => esc_html__( 'Publicly Queryable?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'yes', 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1,"groups-advanced":1}' ), ), 'groups-show-ui' => array( - 'field_title' => 'Show In Admin UI?', + 'field_title' => esc_html__( 'Show In Admin UI?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'yes', 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1,"groups-advanced":1}' ), ), 'groups-show-in-nav-menus' => array( - 'field_title' => 'Show In Navigation Menus?', + 'field_title' => esc_html__( 'Show In Navigation Menus?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'no', @@ -288,27 +272,27 @@ public function get_settings_definitions() { ), ), ), - $this->plugin_settings_name . '_calendar' => array( - 'page_title' => 'Public Events', + 'ccb_core_settings_calendar' => array( + 'page_title' => esc_html__( 'Public Events', 'ccb-core' ), 'sections' => array( 'calendar' => array( - 'section_title' => 'Public Events', + 'section_title' => esc_html__( 'Public Events', 'ccb-core' ), 'fields' => array( 'calendar-enabled' => array( - 'field_title' => 'Enable Events', + 'field_title' => esc_html__( 'Enable Events', 'ccb-core' ), 'field_render_function' => 'render_switch', 'field_validation' => 'switch', ), 'calendar-name' => array( - 'field_title' => 'Event Display Name', + 'field_title' => esc_html__( 'Event Display Name', 'ccb-core' ), 'field_render_function' => 'render_text', - 'field_placeholder' => 'Events', + 'field_placeholder' => esc_html__( 'Events', 'ccb-core' ), 'field_validation' => 'alphanumeric_extended', 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1}' ), 'field_tooltip' => 'This is what you call the events in your church (i.e. Meetups, Hangouts, etc.).', ), 'calendar-slug' => array( - 'field_title' => 'Events URL Name', + 'field_title' => esc_html__( 'Events URL Name', 'ccb-core' ), 'field_render_function' => 'render_text', 'field_placeholder' => 'events', 'field_validation' => 'slug', @@ -316,17 +300,17 @@ public function get_settings_definitions() { 'field_tooltip' => 'This is typically where your theme will display all the events. WordPress calls this a "slug".', ), 'calendar-advanced' => array( - 'field_title' => 'Enable Advanced Settings (Optional)', + 'field_title' => esc_html__( 'Enable Advanced Settings (Optional)', 'ccb-core' ), 'field_render_function' => 'render_switch', 'field_validation' => 'switch', 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1}' ), ), 'calendar-date-range-type' => array( - 'field_title' => 'Date Range Type', + 'field_title' => esc_html__( 'Date Range Type', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'relative' => 'Relative Range', - 'specific' => 'Specific Range' + 'relative' => esc_html__( 'Relative Range', 'ccb-core' ), + 'specific' => esc_html__( 'Specific Range', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'relative', @@ -334,7 +318,7 @@ public function get_settings_definitions() { 'field_tooltip' => 'Relative: For example, always get the events from \'One week ago\', up to \'Eight weeks from now\'.
This is the best setting for most churches.

Specific: For example, only get events from \'6/1/2015\' to \'12/1/2015\'.
This setting is best if you want to tightly manage the events that get published.', ), 'calendar-relative-weeks-past' => array( - 'field_title' => 'How Far Back?', + 'field_title' => esc_html__( 'How Far Back?', 'ccb-core' ), 'field_render_function' => 'render_slider', 'field_options' => array( 'min' => '0', @@ -347,7 +331,7 @@ public function get_settings_definitions() { 'field_tooltip' => 'Every time we synchronize, how many weeks in the past should we look?(0 would be "today")', ), 'calendar-relative-weeks-future' => array( - 'field_title' => 'How Into The Future?', + 'field_title' => esc_html__( 'How Into The Future?', 'ccb-core' ), 'field_render_function' => 'render_slider', 'field_options' => array( 'min' => '1', @@ -360,58 +344,58 @@ public function get_settings_definitions() { 'field_tooltip' => 'Every time we synchronize, how many weeks in the future should we look?', ), 'calendar-specific-start' => array( - 'field_title' => 'Specific Start Date', + 'field_title' => esc_html__( 'Specific Start Date', 'ccb-core' ), 'field_render_function' => 'render_date_picker', 'field_validation' => '', 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1,"calendar-date-range-type":"specific"}' ), 'field_tooltip' => 'When synchronizing, we should get events that start after this date.
(Leave empty to always start "today")', ), 'calendar-specific-end' => array( - 'field_title' => 'Specific End Date', + 'field_title' => esc_html__( 'Specific End Date', 'ccb-core' ), 'field_render_function' => 'render_date_picker', 'field_validation' => '', 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1,"calendar-date-range-type":"specific"}' ), 'field_tooltip' => 'When synchronizing, we should get events that start before this date.
(Setting this too far into the future may cause the API to timeout)', ), 'calendar-exclude-from-search' => array( - 'field_title' => 'Exclude From Search?', + 'field_title' => esc_html__( 'Exclude From Search?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'no', 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1}' ), ), 'calendar-publicly-queryable' => array( - 'field_title' => 'Publicly Queryable?', + 'field_title' => esc_html__( 'Publicly Queryable?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'yes', 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1}' ), ), 'calendar-show-ui' => array( - 'field_title' => 'Show In Admin UI?', + 'field_title' => esc_html__( 'Show In Admin UI?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'yes', 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1}' ), ), 'calendar-show-in-nav-menus' => array( - 'field_title' => 'Show In Navigation Menus?', + 'field_title' => esc_html__( 'Show In Navigation Menus?', 'ccb-core' ), 'field_render_function' => 'render_radio', 'field_options' => array( - 'yes' => 'Yes', - 'no' => 'No' + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), ), 'field_validation' => '', 'field_default' => 'no', @@ -421,19 +405,19 @@ public function get_settings_definitions() { ), ), ), - $this->plugin_settings_name . '_sync' => array( - 'page_title' => 'Synchronize', + 'ccb_core_settings_sync' => array( + 'page_title' => esc_html__( 'Synchronize', 'ccb-core' ), 'sections' => array( 'synchronize' => array( - 'section_title' => 'Synchronize', + 'section_title' => esc_html__( 'Synchronize', 'ccb-core' ), 'fields' => array( 'auto-sync' => array( - 'field_title' => 'Enable Auto Sync', + 'field_title' => esc_html__( 'Enable Auto Sync', 'ccb-core' ), 'field_render_function' => 'render_switch', 'field_validation' => 'switch', ), 'auto-sync-timeout' => array( - 'field_title' => 'Cache Expiration', + 'field_title' => esc_html__( 'Cache Expiration', 'ccb-core' ), 'field_render_function' => 'render_slider', 'field_options' => array( 'min' => '10', @@ -446,11 +430,11 @@ public function get_settings_definitions() { 'field_tooltip' => 'We keep a local copy (cache) of your Church Community Builder data for the best performance.
How often (in minutes) should we check for new data?
90 minutes is recommended.', ), 'manual-sync' => array( - 'field_title' => 'Manual Sync', + 'field_title' => esc_html__( 'Manual Sync', 'ccb-core' ), 'field_render_function' => 'render_manual_sync', ), 'latest-results' => array( - 'field_title' => 'Latest Sync Results', + 'field_title' => esc_html__( 'Latest Sync Results', 'ccb-core' ), 'field_render_function' => 'render_latest_results', ), ), @@ -459,4 +443,34 @@ public function get_settings_definitions() { ), ); } + + /** + * Helper function to create a name/value hash for quick validation + * + * @access protected + * @since 0.9.0 + * @return array $mapping + */ + protected function generate_validation_hash() { + $mapping = array(); + $page_id = isset( $_POST['option_page'] ) ? sanitize_text_field( wp_unslash( $_POST['option_page'] ) ) : false; // Input var okay. + $settings_definitions = $this->get_settings_definitions(); + + foreach ( $settings_definitions[ $page_id ]['sections'] as $section ) { + if ( ! empty( $section['fields'] ) ) { + foreach ( $section['fields'] as $field_id => $field ) { + if ( isset( $field['field_validation'] ) ) { + $mapping[ $field_id ] = array( + 'field_title' => $field['field_title'], + 'field_validation' => $field['field_validation'], + ); + } else { + $mapping[ $field_id ] = false; + } + } + } + } + return $mapping; + } + } diff --git a/admin/class-ccb-core-sync.php b/includes/class-ccb-core-sync.php similarity index 99% rename from admin/class-ccb-core-sync.php rename to includes/class-ccb-core-sync.php index 988e824..8d7bb99 100644 --- a/admin/class-ccb-core-sync.php +++ b/includes/class-ccb-core-sync.php @@ -2,7 +2,7 @@ /** * Synchronize CCB API data * - * @link http://jaredcobb.com/ccb-core + * @link https://www.wpccb.com * @since 0.9.0 * * @package CCB_Core @@ -12,13 +12,13 @@ /** * Synchronize CCB API data * - * Handles the cURL request, throttling, and caching of data + * Handles the remote request, throttling, and caching of data * * @package CCB_Core - * @subpackage CCB_Core/admin + * @subpackage CCB_Core/includes * @author Jared Cobb */ -class CCB_Core_Sync extends CCB_Core_Plugin { +class CCB_Core_Sync { /** * The subdomain of the ccb church installation diff --git a/includes/class-ccb-core.php b/includes/class-ccb-core.php index 5eb59a6..d52fa45 100644 --- a/includes/class-ccb-core.php +++ b/includes/class-ccb-core.php @@ -5,11 +5,9 @@ * A class definition that includes attributes and functions used across both the * public-facing side of the site and the dashboard. * - * @link http://jaredcobb.com/ccb-core - * @since 0.9.0 - * - * @package CCB_Core - * @subpackage CCB_Core/includes + * @link https://www.wpccb.com + * @package CCB_Core + * @subpackage CCB_Core/includes */ /** @@ -21,31 +19,11 @@ * Also maintains the unique identifier of this plugin as well as the current * version of the plugin. * - * @since 0.9.0 * @package CCB_Core * @subpackage CCB_Core/includes * @author Jared Cobb */ -class CCB_Core extends CCB_Core_Plugin { - - /** - * The loader that's responsible for maintaining and registering all hooks that power - * the plugin. - * - * @since 0.9.0 - * @access protected - * @var CCB_Core_Loader $loader Maintains and registers all hooks for the plugin. - */ - protected $loader; - - /** - * A helper for getting the plugin_basename - * - * @since 0.9.0 - * @access protected - * @var CCB_Core_Loader $loader Maintains and registers all hooks for the plugin. - */ - protected $plugin_basename; +class CCB_Core { /** * Define the core functionality of the plugin. @@ -56,119 +34,250 @@ class CCB_Core extends CCB_Core_Plugin { * * @since 0.9.0 */ - public function __construct( $plugin_basename ) { - - parent::__construct(); - $this->plugin_basename = $plugin_basename; + public function __construct() { $this->load_dependencies(); - $this->set_locale(); - $this->define_admin_hooks(); - + $this->define_hooks(); } /** * Load the required dependencies for this plugin. * - * Also create an instance of the loader which will be used to register the hooks - * with WordPress. + * @since 0.9.0 + * @access private + */ + private function load_dependencies() { + + // Encryption class to provide better security and ease of use. + require_once CCB_CORE_PATH . 'lib/class-ccb-core-vendor-encryption.php'; + + // A generic helper class with commonly used mehtods. + require_once CCB_CORE_PATH . 'includes/class-ccb-core-helpers.php'; + + // The classes that define options and settings for the plugin. + require_once CCB_CORE_PATH . 'includes/class-ccb-core-settings.php'; + require_once CCB_CORE_PATH . 'includes/class-ccb-core-settings-page.php'; + require_once CCB_CORE_PATH . 'includes/class-ccb-core-settings-section.php'; + require_once CCB_CORE_PATH . 'includes/class-ccb-core-settings-field.php'; + + // The class that handles data synchronization between CCB and the local cache. + require_once CCB_CORE_PATH . 'includes/class-ccb-core-sync.php'; + + // Custom Post Type classes. + require_once CCB_CORE_PATH . 'includes/post-types/class-ccb-core-cpt.php'; + require_once CCB_CORE_PATH . 'includes/post-types/class-ccb-core-group.php'; + require_once CCB_CORE_PATH . 'includes/post-types/class-ccb-core-calendar.php'; + + // Custom Taxonomy classes. + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-taxonomy.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-calendar-event-type.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-calendar-group-name.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-calendar-grouping-name.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-group-area.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-group-day.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-group-department.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-group-tag.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-group-time.php'; + require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-group-type.php'; + + } + + /** + * Register all of the hooks related to the dashboard functionality + * of the plugin. * * @since 0.9.0 * @access private */ - private function load_dependencies() { + private function define_hooks() { - // encryption class to provide better security and ease of use - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'lib/Encryption/Encryption.php'; + // Internationalization. + add_action( 'plugins_loaded', array( $this, 'load_plugin_textdomain' ) ); - // the class responsible for orchestrating the actions and filters of the core plugin. - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-ccb-core-loader.php'; + // Plugin settings, menus, options. + add_filter( 'plugin_action_links_' . CCB_CORE_BASENAME, array( $this, 'add_settings_link' ) ); - // the class responsible for defining internationalization functionality of the plugin. - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-ccb-core-i18n.php'; + // Setup settings pages. + add_action( 'admin_menu', array( $this, 'initialize_settings_menu' ) ); + add_action( 'admin_init', array( $this, 'initialize_settings' ) ); - // the class that defines options and settings for the plugin - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-ccb-core-settings.php'; - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-ccb-core-settings-page.php'; - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-ccb-core-settings-section.php'; - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-ccb-core-settings-field.php'; + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); - // the class responsible for defining all actions that occur in the Dashboard. - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-ccb-core-admin.php'; + /*// Cron related hooks. + $this->loader->add_action( 'schedule_auto_refresh', $plugin_admin, 'auto_sync' ); + $this->loader->add_action( 'wp_loaded', $plugin_admin, 'check_auto_refresh' ); - // the class that handles data synchronization between CCB and the local cache - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-ccb-core-sync.php'; + // User initiated actions. + $this->loader->add_action( 'pre_update_option_' . $this->plugin_settings_name, $plugin_admin, 'update_settings_callback', 10, 2 ); + $this->loader->add_action( 'schedule_flush_rewrite_rules', $plugin_admin, 'flush_rewrite_rules_event' ); - // the class that handles data synchronization between CCB and the local cache - require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-ccb-core-cpts.php'; + // All backend ajax hooks. + $this->loader->add_action( 'wp_ajax_sync', $plugin_admin, 'ajax_sync' ); + $this->loader->add_action( 'wp_ajax_poll_sync', $plugin_admin, 'ajax_poll_sync' ); + $this->loader->add_action( 'wp_ajax_test_credentials', $plugin_admin, 'ajax_test_credentials' ); + $this->loader->add_action( 'wp_ajax_get_latest_sync', $plugin_admin, 'ajax_get_latest_sync' );*/ - // instantiate the loader - $this->loader = new CCB_Core_Loader(); + } + + /** + * Load the plugin text domain for translation. + * + * @since 0.9.0 + */ + public function load_plugin_textdomain() { + + load_plugin_textdomain( + 'ccb-core', + false, + dirname( CCB_CORE_BASENAME ) . '/languages/' + ); } /** - * Define the locale for this plugin for internationalization. + * Create a helpful settings link on the plugin page * - * Uses the CCB_Core_i18n class in order to set the domain and to register the hook - * with WordPress. + * @param array $links An array of links. + * @access public + * @since 0.9.0 + * @return array + */ + public function add_settings_link( $links ) { + $links[] = '' . esc_html__( 'Settings', 'ccb-core' ) . ''; + return $links; + } + + /** + * Initialize the Settings Menu and Page * + * @access public * @since 0.9.0 - * @access private + * @return void */ - private function set_locale() { + public function initialize_settings_menu() { - $plugin_i18n = new CCB_Core_i18n(); - $this->loader->add_action( 'plugins_loaded', $plugin_i18n, 'load_plugin_textdomain' ); + $settings = new CCB_Core_Settings(); + $settings_page = new CCB_Core_Settings_Page( 'ccb_core_settings' ); + add_menu_page( + __( 'Church Community Builder Core API', 'ccb-core' ), + __( 'CCB Core API', 'ccb-core' ), + 'manage_options', + 'ccb_core_settings', + '__return_null', + 'dashicons-update', + '80.9' + ); + + foreach ( $settings->get_settings_definitions() as $page_id => $page ) { + $settings_page = new CCB_Core_Settings_Page( $page_id ); + add_submenu_page( + 'ccb_core_settings', + $page['page_title'], + $page['page_title'], + 'manage_options', + $page_id, + array( + $settings_page, + 'render_page', + ) + ); + } } /** - * Register all of the hooks related to the dashboard functionality - * of the plugin. + * Initialize the Settings * + * @access public * @since 0.9.0 - * @access private + * @return void */ - private function define_admin_hooks() { + public function initialize_settings() { - $plugin_admin = new CCB_Core_Admin(); + $settings = new CCB_Core_Settings(); - $this->loader->add_action( 'init', $plugin_admin, 'initialize_custom_post_types' ); - $this->loader->add_action( 'admin_menu', $plugin_admin, 'initialize_settings_menu' ); - $this->loader->add_action( 'admin_init', $plugin_admin, 'initialize_settings' ); - $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' ); - $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' ); - $this->loader->add_filter( 'plugin_action_links_' . $this->plugin_basename, $plugin_admin, 'add_settings_link' ); - $this->loader->add_action( 'schedule_auto_refresh', $plugin_admin, 'auto_sync' ); - $this->loader->add_action( 'wp_loaded', $plugin_admin, 'check_auto_refresh' ); - $this->loader->add_action( 'pre_update_option_' . $this->plugin_settings_name, $plugin_admin, 'update_settings_callback', 10, 2 ); - $this->loader->add_action( 'schedule_flush_rewrite_rules', $plugin_admin, 'flush_rewrite_rules_event' ); + foreach ( $settings->get_settings_definitions() as $page_id => $page ) { - // all backend ajax hooks - $this->loader->add_action( 'wp_ajax_sync', $plugin_admin, 'ajax_sync' ); - $this->loader->add_action( 'wp_ajax_poll_sync', $plugin_admin, 'ajax_poll_sync' ); - $this->loader->add_action( 'wp_ajax_test_credentials', $plugin_admin, 'ajax_test_credentials' ); - $this->loader->add_action( 'wp_ajax_get_latest_sync', $plugin_admin, 'ajax_get_latest_sync' ); + register_setting( $page_id, 'ccb_core_settings', array( $settings, 'validate_settings' ) ); + + foreach ( $page['sections'] as $section_id => $section ) { + + $settings_section = new CCB_Core_Settings_Section( $section_id, $section ); + add_settings_section( + $section_id, + $section['section_title'], + array( + $settings_section, + 'render_section', + ), + $page_id + ); + + if ( ! empty( $section['fields'] ) ) { + foreach ( $section['fields'] as $field_id => $field ) { + + $settings_field = new CCB_Core_Settings_Field( $field_id, $field ); + add_settings_field( + $field_id, + $field['field_title'], + array( + $settings_field, + 'render_field', + ), + $page_id, + $section_id + ); + + } + } + + } + + } } /** - * Run the loader to execute all of the hooks with WordPress. + * Register the stylesheets for the dashboard. * - * @since 0.9.0 + * @param string $hook Current admin page. + * @return void */ - public function run() { - $this->loader->run(); + public function enqueue_styles( $hook ) { + + if ( false !== stristr( $hook, 'ccb_core_settings' ) ) { + wp_enqueue_style( 'ccb-core', CCB_CORE_URL . 'css/ccb-core-admin.css', array(), CCB_CORE_VERSION, 'all' ); + wp_enqueue_style( 'switchery', CCB_CORE_URL . 'css/vendor/switchery.min.css', array(), CCB_CORE_VERSION, 'all' ); + wp_enqueue_style( 'powerange', CCB_CORE_URL . 'css/vendor/powerange.min.css', array(), CCB_CORE_VERSION, 'all' ); + wp_enqueue_style( 'picker', CCB_CORE_URL . 'css/vendor/default.css', array(), CCB_CORE_VERSION, 'all' ); + wp_enqueue_style( 'picker-date', CCB_CORE_URL . 'css/vendor/default.date.css', array(), CCB_CORE_VERSION, 'all' ); + wp_enqueue_style( 'tipr', CCB_CORE_URL . 'css/vendor/tipr.css', array(), CCB_CORE_VERSION, 'all' ); + } + } /** - * The reference to the class that orchestrates the hooks with the plugin. + * Register the scripts for the dashboard. * - * @since 0.9.0 - * @return CCB_Core_Loader Orchestrates the hooks of the plugin. + * @param string $hook Current admin page. + * @return void */ - public function get_loader() { - return $this->loader; + public function enqueue_scripts( $hook ) { + + if ( false !== stristr( $hook, 'ccb_core_settings' ) ) { + wp_enqueue_script( 'ccb-core', CCB_CORE_URL . 'js/ccb-core-admin.js', array( 'jquery' ), CCB_CORE_VERSION, false ); + wp_enqueue_script( 'switchery', CCB_CORE_URL . 'js/vendor/switchery.min.js', array( 'jquery' ), CCB_CORE_VERSION, false ); + wp_enqueue_script( 'powerange', CCB_CORE_URL . 'js/vendor/powerange.min.js', array( 'jquery' ), CCB_CORE_VERSION, false ); + wp_enqueue_script( 'picker', CCB_CORE_URL . 'js/vendor/picker.js', array( 'jquery' ), CCB_CORE_VERSION, false ); + wp_enqueue_script( 'picker-date', CCB_CORE_URL . 'js/vendor/picker.date.js', array( 'picker' ), CCB_CORE_VERSION, false ); + wp_enqueue_script( 'tipr', CCB_CORE_URL . 'js/vendor/tipr.min.js', array( 'jquery' ), CCB_CORE_VERSION, false ); + wp_localize_script( 'ccb-core', 'CCB_CORE_SETTINGS', array( + 'nextNonce' => wp_create_nonce( 'ccb-core-nonce' ), + ) + ); + } + } + } diff --git a/includes/post-types/class-ccb-core-calendar.php b/includes/post-types/class-ccb-core-calendar.php new file mode 100644 index 0000000..ece256a --- /dev/null +++ b/includes/post-types/class-ccb-core-calendar.php @@ -0,0 +1,70 @@ + + */ +class CCB_Core_Calendar extends CCB_Core_CPT { + + /** + * Name of the post type + * + * @var string + */ + public $name = 'ccb-core-calendar'; + + /** + * Setup the default CPT options + * + * @since 1.0.0 + * @return array Default options for register_post_type + */ + public function get_cpt_defaults() { + return array( + 'labels' => array( + 'name' => __( 'Events', 'ccb-core' ), + 'singular_name' => __( 'Event', 'ccb-core' ), + 'all_items' => __( 'All Events', 'ccb-core' ), + 'add_new' => __( 'Add New', 'ccb-core' ), + 'add_new_item' => __( 'Add New Event', 'ccb-core' ), + 'edit' => __( 'Edit', 'ccb-core' ), + 'edit_item' => __( 'Edit Event', 'ccb-core' ), + 'new_item' => __( 'New Event', 'ccb-core' ), + 'view_item' => __( 'View Event', 'ccb-core' ), + 'search_items' => __( 'Search Events', 'ccb-core' ), + 'not_found' => __( 'Nothing found in the Database.', 'ccb-core' ), + 'not_found_in_trash' => __( 'Nothing found in Trash', 'ccb-core' ), + 'parent_item_colon' => '', + ), + 'description' => __( 'These are the events that are synchronized with your Church Community Builder software.', 'ccb-core' ), + 'public' => true, + 'publicly_queryable' => true, + 'exclude_from_search' => false, + 'show_ui' => true, + 'show_in_nav_menus' => true, + 'query_var' => true, + 'menu_position' => 8, + 'menu_icon' => 'dashicons-calendar', + 'rewrite' => array( 'slug' => 'events' ), + 'has_archive' => 'events', + 'capability_type' => 'post', + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields', 'sticky' ), + ); + } + +} + +new CCB_Core_Calendar(); diff --git a/includes/post-types/class-ccb-core-cpt.php b/includes/post-types/class-ccb-core-cpt.php new file mode 100644 index 0000000..f55fd73 --- /dev/null +++ b/includes/post-types/class-ccb-core-cpt.php @@ -0,0 +1,129 @@ + + */ +abstract class CCB_Core_CPT { + + /** + * Name of the post type + * + * @var string + */ + public $name; + + /** + * The specific custom post type options + * + * @var array + */ + protected $cpt_options = array(); + + /** + * Initialize the class + */ + public function __construct() { + + $plugin_options = CCB_Core_Helpers::instance()->get_options(); + + // If this CPT is enabled, merge the defaults and set the registration hook. + if ( ! empty( $plugin_options[ $this->name ]['enabled'] ) ) { + $this->cpt_options = wp_parse_args( $this->get_user_cpt_options( $plugin_options ), $this->get_cpt_defaults() ); + add_action( 'init', array( $this, 'register_post_type' ) ); + } + + } + + /** + * Get the dynamic CPT options based on + * what the user may have set + * + * @param array $plugin_options The entire options array. + * @return array + */ + protected function get_user_cpt_options( $plugin_options ) { + + $user_cpt_options = array(); + + if ( ! empty( $plugin_options[ $this->name ]['cpt_options']['name'] ) ) { + $user_cpt_options['labels']['name'] = $plugin_options[ $this->name ]['cpt_options']['name']; + $user_cpt_options['labels']['all_items'] = sprintf( __( 'All %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['name'] ); + $user_cpt_options['labels']['search_items'] = sprintf( __( 'Search %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['name'] ); + } + + if ( ! empty( $plugin_options[ $this->name ]['cpt_options']['singular_name'] ) ) { + $user_cpt_options['labels']['singular_name'] = $plugin_options[ $this->name ]['cpt_options']['singular_name']; + $user_cpt_options['labels']['add_new_item'] = sprintf( __( 'Add New %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['singular_name'] ); + $user_cpt_options['labels']['edit_item'] = sprintf( __( 'Edit %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['singular_name'] ); + $user_cpt_options['labels']['new_item'] = sprintf( __( 'New %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['singular_name'] ); + $user_cpt_options['labels']['view_item'] = sprintf( __( 'View %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['singular_name'] ); + } + + if ( ! empty( $plugin_options[ $this->name ]['cpt_options']['slug'] ) ) { + $user_cpt_options['rewrite'] = array( 'slug' => $plugin_options[ $this->name ]['cpt_options']['slug'] ); + $user_cpt_options['has_archive'] = $plugin_options[ $this->name ]['cpt_options']['slug']; + } + + // The remaining options are boolean, so directly set their values if the option is set. + if ( isset( $plugin_options[ $this->name ]['cpt_options']['publicly_queryable'] ) ) { + $user_cpt_options['publicly_queryable'] = $plugin_options[ $this->name ]['cpt_options']['publicly_queryable']; + } + + if ( isset( $plugin_options[ $this->name ]['cpt_options']['exclude_from_search'] ) ) { + $user_cpt_options['exclude_from_search'] = $plugin_options[ $this->name ]['cpt_options']['exclude_from_search']; + } + + if ( isset( $plugin_options[ $this->name ]['cpt_options']['show_ui'] ) ) { + $user_cpt_options['show_ui'] = $plugin_options[ $this->name ]['cpt_options']['show_ui']; + } + + if ( isset( $plugin_options[ $this->name ]['cpt_options']['show_in_nav_menus'] ) ) { + $user_cpt_options['show_in_nav_menus'] = $plugin_options[ $this->name ]['cpt_options']['show_in_nav_menus']; + } + + return $user_cpt_options; + + } + + /** + * Register the custom post type + * + * @access public + * @since 1.0.0 + * @return void + */ + public function register_post_type() { + register_post_type( $this->name, $this->cpt_options ); + } + + /** + * Get the post type object. + * + * @return object + */ + public function get_post_type_object() { + return get_post_type_object( $this->name ); + } + + /** + * Setup the default CPT options + * + * @since 1.0.0 + * @return array Default options for register_post_type + */ + abstract public function get_cpt_defaults(); + +} diff --git a/admin/class-ccb-core-cpts.php b/includes/post-types/class-ccb-core-cpts.php similarity index 99% rename from admin/class-ccb-core-cpts.php rename to includes/post-types/class-ccb-core-cpts.php index a7a0cab..fbee973 100644 --- a/admin/class-ccb-core-cpts.php +++ b/includes/post-types/class-ccb-core-cpts.php @@ -2,7 +2,7 @@ /** * Custom post types used in this plugin * - * @link http://jaredcobb.com/ccb-core + * @link https://www.wpccb.com * @since 0.9.0 * * @package CCB_Core @@ -16,7 +16,7 @@ * @subpackage CCB_Core/admin * @author Jared Cobb */ -class CCB_Core_CPTs extends CCB_Core_Plugin { +class CCB_Core_CPT { /** * The options we should use to register the groups CPT diff --git a/includes/post-types/class-ccb-core-group.php b/includes/post-types/class-ccb-core-group.php new file mode 100644 index 0000000..964b8be --- /dev/null +++ b/includes/post-types/class-ccb-core-group.php @@ -0,0 +1,70 @@ + + */ +class CCB_Core_Group extends CCB_Core_CPT { + + /** + * Name of the post type + * + * @var string + */ + public $name = 'ccb-core-groups'; + + /** + * Setup the default CPT options + * + * @since 1.0.0 + * @return array Default options for register_post_type + */ + public function get_cpt_defaults() { + return array( + 'labels' => array( + 'name' => __( 'Groups', 'ccb-core' ), + 'singular_name' => __( 'Group', 'ccb-core' ), + 'all_items' => __( 'All Groups', 'ccb-core' ), + 'add_new' => __( 'Add New', 'ccb-core' ), + 'add_new_item' => __( 'Add New Group', 'ccb-core' ), + 'edit' => __( 'Edit', 'ccb-core' ), + 'edit_item' => __( 'Edit Group', 'ccb-core' ), + 'new_item' => __( 'New Group', 'ccb-core' ), + 'view_item' => __( 'View Group', 'ccb-core' ), + 'search_items' => __( 'Search Groups', 'ccb-core' ), + 'not_found' => __( 'Nothing found in the Database.', 'ccb-core' ), + 'not_found_in_trash' => __( 'Nothing found in Trash', 'ccb-core' ), + 'parent_item_colon' => '', + ), + 'description' => __( 'These are the groups that are synchronized with your Church Community Builder software.', 'ccb-core' ), + 'public' => true, + 'publicly_queryable' => true, + 'exclude_from_search' => false, + 'show_ui' => true, + 'show_in_nav_menus' => true, + 'query_var' => true, + 'menu_position' => 8, + 'menu_icon' => 'dashicons-groups', + 'rewrite' => array( 'slug' => 'groups' ), + 'has_archive' => 'groups', + 'capability_type' => 'post', + 'hierarchical' => false, + 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields', 'sticky' ), + ); + } + +} + +new CCB_Core_Group(); diff --git a/includes/taxonomies/class-ccb-core-calendar-event-type.php b/includes/taxonomies/class-ccb-core-calendar-event-type.php new file mode 100644 index 0000000..7ba1615 --- /dev/null +++ b/includes/taxonomies/class-ccb-core-calendar-event-type.php @@ -0,0 +1,65 @@ + + */ +class CCB_Core_Calendar_Event_Type extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'calendar_event_type'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-calendar' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Types', 'ccb-core' ), + 'singular_name' => __( 'Type', 'ccb-core' ), + 'search_items' => __( 'Search Types', 'ccb-core' ), + 'all_items' => __( 'All Types', 'ccb-core' ), + 'parent_item' => __( 'Parent Type', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Type:', 'ccb-core' ), + 'edit_item' => __( 'Edit Type', 'ccb-core' ), + 'update_item' => __( 'Update Type', 'ccb-core' ), + 'add_new_item' => __( 'Add New Type', 'ccb-core' ), + 'new_item_name' => __( 'New Type', 'ccb-core' ), + ), + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => 'event_type', // The field key from the CCB API. + ); + } + +} + +new CCB_Core_Calendar_Event_Type(); diff --git a/includes/taxonomies/class-ccb-core-calendar-group-name.php b/includes/taxonomies/class-ccb-core-calendar-group-name.php new file mode 100644 index 0000000..cfaa411 --- /dev/null +++ b/includes/taxonomies/class-ccb-core-calendar-group-name.php @@ -0,0 +1,65 @@ + + */ +class CCB_Core_Calendar_Group_Name extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'calendar_group_name'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-calendar' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Group Names', 'ccb-core' ), + 'singular_name' => __( 'Group Name', 'ccb-core' ), + 'search_items' => __( 'Search Group Names', 'ccb-core' ), + 'all_items' => __( 'All Group Names', 'ccb-core' ), + 'parent_item' => __( 'Parent Group Name', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Group Name:', 'ccb-core' ), + 'edit_item' => __( 'Edit Group Name', 'ccb-core' ), + 'update_item' => __( 'Update Group Name', 'ccb-core' ), + 'add_new_item' => __( 'Add New Group Name', 'ccb-core' ), + 'new_item_name' => __( 'New Group Name', 'ccb-core' ), + ), + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => 'group_name', // The field key from the CCB API. + ); + } + +} + +new CCB_Core_Calendar_Group_Name(); diff --git a/includes/taxonomies/class-ccb-core-calendar-grouping-name.php b/includes/taxonomies/class-ccb-core-calendar-grouping-name.php new file mode 100644 index 0000000..01fe316 --- /dev/null +++ b/includes/taxonomies/class-ccb-core-calendar-grouping-name.php @@ -0,0 +1,65 @@ + + */ +class CCB_Core_Calendar_Grouping_Name extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'calendar_grouping_name'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-calendar' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Grouping Names', 'ccb-core' ), + 'singular_name' => __( 'Grouping Name', 'ccb-core' ), + 'search_items' => __( 'Search Grouping Names', 'ccb-core' ), + 'all_items' => __( 'All Grouping Names', 'ccb-core' ), + 'parent_item' => __( 'Parent Grouping Name', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Grouping Name:', 'ccb-core' ), + 'edit_item' => __( 'Edit Grouping Name', 'ccb-core' ), + 'update_item' => __( 'Update Grouping Name', 'ccb-core' ), + 'add_new_item' => __( 'Add New Grouping Name', 'ccb-core' ), + 'new_item_name' => __( 'New Grouping Name', 'ccb-core' ), + ), + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => 'grouping_name', // The field key from the CCB API. + ); + } + +} + +new CCB_Core_Calendar_Grouping_Name(); diff --git a/includes/taxonomies/class-ccb-core-group-area.php b/includes/taxonomies/class-ccb-core-group-area.php new file mode 100644 index 0000000..f330957 --- /dev/null +++ b/includes/taxonomies/class-ccb-core-group-area.php @@ -0,0 +1,65 @@ + + */ +class CCB_Core_Group_Area extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'group_areas'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-groups' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Areas', 'ccb-core' ), + 'singular_name' => __( 'Area', 'ccb-core' ), + 'search_items' => __( 'Search Areas', 'ccb-core' ), + 'all_items' => __( 'All Areas', 'ccb-core' ), + 'parent_item' => __( 'Parent Area', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Area:', 'ccb-core' ), + 'edit_item' => __( 'Edit Area', 'ccb-core' ), + 'update_item' => __( 'Update Area', 'ccb-core' ), + 'add_new_item' => __( 'Add New Area', 'ccb-core' ), + 'new_item_name' => __( 'New Area', 'ccb-core' ), + ), + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => 'area', // The field key from the CCB API. + ); + } + +} + +new CCB_Core_Group_Area(); diff --git a/includes/taxonomies/class-ccb-core-group-day.php b/includes/taxonomies/class-ccb-core-group-day.php new file mode 100644 index 0000000..819474f --- /dev/null +++ b/includes/taxonomies/class-ccb-core-group-day.php @@ -0,0 +1,63 @@ + + */ +class CCB_Core_Group_Day extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'group_days'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-groups' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Days', 'ccb-core' ), + 'singular_name' => __( 'Day', 'ccb-core' ), + 'search_items' => __( 'Search Days', 'ccb-core' ), + 'all_items' => __( 'All Days', 'ccb-core' ), + 'parent_item' => __( 'Parent Day', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Day:', 'ccb-core' ), + 'edit_item' => __( 'Edit Day', 'ccb-core' ), + 'update_item' => __( 'Update Day', 'ccb-core' ), + 'add_new_item' => __( 'Add New Day', 'ccb-core' ), + 'new_item_name' => __( 'New Day', 'ccb-core' ), + ), + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => 'meeting_day', // The field key from the CCB API. + ); + } + +} diff --git a/includes/taxonomies/class-ccb-core-group-department.php b/includes/taxonomies/class-ccb-core-group-department.php new file mode 100644 index 0000000..e051967 --- /dev/null +++ b/includes/taxonomies/class-ccb-core-group-department.php @@ -0,0 +1,65 @@ + + */ +class CCB_Core_Group_Department extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'group_departments'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-groups' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Departments', 'ccb-core' ), + 'singular_name' => __( 'Department', 'ccb-core' ), + 'search_items' => __( 'Search Departments', 'ccb-core' ), + 'all_items' => __( 'All Departments', 'ccb-core' ), + 'parent_item' => __( 'Parent Department', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Department:', 'ccb-core' ), + 'edit_item' => __( 'Edit Department', 'ccb-core' ), + 'update_item' => __( 'Update Department', 'ccb-core' ), + 'add_new_item' => __( 'Add New Department', 'ccb-core' ), + 'new_item_name' => __( 'New Department', 'ccb-core' ), + ), + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => 'department', // The field key from the CCB API. + ); + } + +} + +new CCB_Core_Group_Department(); diff --git a/includes/taxonomies/class-ccb-core-group-tag.php b/includes/taxonomies/class-ccb-core-group-tag.php new file mode 100644 index 0000000..7734efb --- /dev/null +++ b/includes/taxonomies/class-ccb-core-group-tag.php @@ -0,0 +1,65 @@ + + */ +class CCB_Core_Group_Tag extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'group_tags'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-groups' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Group Tags', 'ccb-core' ), + 'singular_name' => __( 'Group Tag', 'ccb-core' ), + 'search_items' => __( 'Search Group Tags', 'ccb-core' ), + 'all_items' => __( 'All Group Tags', 'ccb-core' ), + 'parent_item' => __( 'Parent Group Tag', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Group Tag:', 'ccb-core' ), + 'edit_item' => __( 'Edit Group Tag', 'ccb-core' ), + 'update_item' => __( 'Update Group Tag', 'ccb-core' ), + 'add_new_item' => __( 'Add New Group Tag', 'ccb-core' ), + 'new_item_name' => __( 'New Group Tag', 'ccb-core' ), + ), + 'hierarchical' => false, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => array( 'childcare_provided' => __( 'Childcare Provided' ) ), // The field key from the CCB API. + ); + } + +} + +new CCB_Core_Group_Tag(); diff --git a/includes/taxonomies/class-ccb-core-group-time.php b/includes/taxonomies/class-ccb-core-group-time.php new file mode 100644 index 0000000..29bc51c --- /dev/null +++ b/includes/taxonomies/class-ccb-core-group-time.php @@ -0,0 +1,65 @@ + + */ +class CCB_Core_Group_Time extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'group_times'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-groups' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Times', 'ccb-core' ), + 'singular_name' => __( 'Time', 'ccb-core' ), + 'search_items' => __( 'Search Times', 'ccb-core' ), + 'all_items' => __( 'All Times', 'ccb-core' ), + 'parent_item' => __( 'Parent Time', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Time:', 'ccb-core' ), + 'edit_item' => __( 'Edit Time', 'ccb-core' ), + 'update_item' => __( 'Update Time', 'ccb-core' ), + 'add_new_item' => __( 'Add New Time', 'ccb-core' ), + 'new_item_name' => __( 'New Time', 'ccb-core' ), + ), + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => 'meeting_time', // The field key from the CCB API. + ); + } + +} + +new CCB_Core_Group_Time(); diff --git a/includes/taxonomies/class-ccb-core-group-type.php b/includes/taxonomies/class-ccb-core-group-type.php new file mode 100644 index 0000000..156036d --- /dev/null +++ b/includes/taxonomies/class-ccb-core-group-type.php @@ -0,0 +1,65 @@ + + */ +class CCB_Core_Group_Type extends CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name = 'group_types'; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array( 'ccb-core-groups' ); + + /** + * Setup the default taxonomy mappings + * + * @since 1.0.0 + * @return array Default options for register_taxonomy + */ + public static function get_taxonomy_mapping() { + return array( + 'labels' => array( + 'name' => __( 'Types', 'ccb-core' ), + 'singular_name' => __( 'Type', 'ccb-core' ), + 'search_items' => __( 'Search Types', 'ccb-core' ), + 'all_items' => __( 'All Types', 'ccb-core' ), + 'parent_item' => __( 'Parent Type', 'ccb-core' ), + 'parent_item_colon' => __( 'Parent Type:', 'ccb-core' ), + 'edit_item' => __( 'Edit Type', 'ccb-core' ), + 'update_item' => __( 'Update Type', 'ccb-core' ), + 'add_new_item' => __( 'Add New Type', 'ccb-core' ), + 'new_item_name' => __( 'New Type', 'ccb-core' ), + ), + 'hierarchical' => true, + 'show_admin_column' => true, + 'show_ui' => true, + 'query_var' => true, + 'api_mapping' => 'group_type', // The field key from the CCB API. + ); + } + +} + +new CCB_Core_Group_Type(); diff --git a/includes/taxonomies/class-ccb-core-taxonomy.php b/includes/taxonomies/class-ccb-core-taxonomy.php new file mode 100644 index 0000000..aff5458 --- /dev/null +++ b/includes/taxonomies/class-ccb-core-taxonomy.php @@ -0,0 +1,56 @@ + + */ +abstract class CCB_Core_Taxonomy { + + /** + * Name of the taxonomy + * + * @var string + */ + public $name; + + /** + * Object types for this taxonomy + * + * @var array + */ + public $object_types = array(); + + /** + * Initialize the class + */ + public function __construct() { + add_action( 'init', array( $this, 'register_taxonomy' ) ); + } + + /** + * Register the custom taxonomy + * + * @return void + */ + public function register_taxonomy() { + register_taxonomy( $this->name, $this->object_types, static::get_taxonomy_mapping() ); + } + + /** + * Register the taxonomy. + */ + abstract public static function get_taxonomy_mapping(); + +} diff --git a/admin/js/ccb-core-admin.js b/js/ccb-core-admin.js similarity index 94% rename from admin/js/ccb-core-admin.js rename to js/ccb-core-admin.js index 29ffa53..a83ee38 100644 --- a/admin/js/ccb-core-admin.js +++ b/js/ccb-core-admin.js @@ -11,9 +11,9 @@ this.syncPollId = setInterval(this.pollForActiveSync, 10000); this.pollForActiveSync(); - $('.test-login-wrapper .button').on('click', event, this.testCredentials); + $('.test-login-wrapper .button').on('click', window.event, this.testCredentials); - $('.sync-wrapper .button').on('click', event, this.syncData); + $('.sync-wrapper .button').on('click', window.event, this.syncData); var switches = Array.prototype.slice.call(document.querySelectorAll('.js-switch')); switches.forEach(function(html) { @@ -50,7 +50,6 @@ refreshEnabledFields : function() { $('[data-requires]').each(function() { - //var requiredElements = $(this).data('requires').split(' '); var displayField = true; var requiresObject = $(this).data('requires'); @@ -59,13 +58,13 @@ if (requiresObject.hasOwnProperty(key)) { var requiredElement = $("[name='ccb_core_settings[" + key + "]']"); - if (requiredElement.is(':checkbox')) { + if (requiredElement.is('input:checkbox')) { if (!requiredElement.is(':checked')) { displayField = false; break; } } - else if (requiredElement.is(':radio')) { + else if (requiredElement.is('input:radio')) { requiredElement = $("[name='ccb_core_settings[" + key + "]']:checked"); if (requiredElement.val() !== requiresObject[key]) displayField = false; diff --git a/admin/js/vendor/picker.date.js b/js/vendor/picker.date.js similarity index 100% rename from admin/js/vendor/picker.date.js rename to js/vendor/picker.date.js diff --git a/admin/js/vendor/picker.js b/js/vendor/picker.js similarity index 100% rename from admin/js/vendor/picker.js rename to js/vendor/picker.js diff --git a/admin/js/vendor/powerange.min.js b/js/vendor/powerange.min.js similarity index 100% rename from admin/js/vendor/powerange.min.js rename to js/vendor/powerange.min.js diff --git a/admin/js/vendor/switchery.min.js b/js/vendor/switchery.min.js similarity index 100% rename from admin/js/vendor/switchery.min.js rename to js/vendor/switchery.min.js diff --git a/admin/js/vendor/tipr.min.js b/js/vendor/tipr.min.js similarity index 100% rename from admin/js/vendor/tipr.min.js rename to js/vendor/tipr.min.js diff --git a/lib/Encryption/Encryption.php b/lib/Encryption/Encryption.php deleted file mode 100644 index e63de0e..0000000 --- a/lib/Encryption/Encryption.php +++ /dev/null @@ -1,163 +0,0 @@ -cipher = $cipher; - $this->mode = $mode; - $this->rounds = (int) $rounds; - } - - /** - * Decrypt the data with the provided key - * - * @param string $data The encrypted datat to decrypt - * @param string $key The key to use for decryption - * - * @returns string|false The returned string if decryption is successful - * false if it is not - */ - public function decrypt($data, $key) { - $salt = substr($data, 0, 128); - $enc = substr($data, 128, -64); - $mac = substr($data, -64); - - list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key); - - if ($mac !== hash_hmac('sha512', $enc, $macKey, true)) { - return false; - } - - $dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv); - - $data = $this->unpad($dec); - - return $data; - } - - /** - * Encrypt the supplied data using the supplied key - * - * @param string $data The data to encrypt - * @param string $key The key to encrypt with - * - * @returns string The encrypted data - */ - public function encrypt($data, $key) { - $salt = mcrypt_create_iv(128, MCRYPT_RAND); - list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key); - - $data = $this->pad($data); - - $enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv); - - $mac = hash_hmac('sha512', $enc, $macKey, true); - return $salt . $enc . $mac; - } - - /** - * Generates a set of keys given a random salt and a master key - * - * @param string $salt A random string to change the keys each encryption - * @param string $key The supplied key to encrypt with - * - * @returns array An array of keys (a cipher key, a mac key, and a IV) - */ - protected function getKeys($salt, $key) { - $ivSize = mcrypt_get_iv_size($this->cipher, $this->mode); - $keySize = mcrypt_get_key_size($this->cipher, $this->mode); - $length = 2 * $keySize + $ivSize; - - $key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length); - - $cipherKey = substr($key, 0, $keySize); - $macKey = substr($key, $keySize, $keySize); - $iv = substr($key, 2 * $keySize); - return array($cipherKey, $macKey, $iv); - } - - /** - * Stretch the key using the PBKDF2 algorithm - * - * @see http://en.wikipedia.org/wiki/PBKDF2 - * - * @param string $algo The algorithm to use - * @param string $key The key to stretch - * @param string $salt A random salt - * @param int $rounds The number of rounds to derive - * @param int $length The length of the output key - * - * @returns string The derived key. - */ - protected function pbkdf2($algo, $key, $salt, $rounds, $length) { - $size = strlen(hash($algo, '', true)); - $len = ceil($length / $size); - $result = ''; - for ($i = 1; $i <= $len; $i++) { - $tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true); - $res = $tmp; - for ($j = 1; $j < $rounds; $j++) { - $tmp = hash_hmac($algo, $tmp, $key, true); - $res ^= $tmp; - } - $result .= $res; - } - return substr($result, 0, $length); - } - - protected function pad($data) { - $length = mcrypt_get_block_size($this->cipher, $this->mode); - $padAmount = $length - strlen($data) % $length; - if ($padAmount == 0) { - $padAmount = $length; - } - return $data . str_repeat(chr($padAmount), $padAmount); - } - - protected function unpad($data) { - $length = mcrypt_get_block_size($this->cipher, $this->mode); - $last = ord($data[strlen($data) - 1]); - if ($last > $length) return false; - if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) { - return false; - } - return substr($data, 0, -1 * $last); - } -} -?> diff --git a/lib/class-ccb-core-vendor-encryption.php b/lib/class-ccb-core-vendor-encryption.php new file mode 100644 index 0000000..eb82917 --- /dev/null +++ b/lib/class-ccb-core-vendor-encryption.php @@ -0,0 +1,190 @@ +cipher = $cipher; + $this->mode = $mode; + $this->rounds = (int) $rounds; + } + + /** + * Decrypt the data with the provided key + * + * @param string $data The encrypted datat to decrypt. + * @param string $key The key to use for decryption. + * + * @returns string|false The returned string if decryption is successful false if it is not + */ + public function decrypt( $data, $key ) { + $salt = substr( $data, 0, 128 ); + $enc = substr( $data, 128, -64 ); + $mac = substr( $data, -64 ); + + list ( $cipher_key, $mac_key, $iv ) = $this->get_keys( $salt, $key ); + + if ( hash_hmac( 'sha512', $enc, $mac_key, true ) !== $mac ) { + return false; + } + + $dec = mcrypt_decrypt( $this->cipher, $cipher_key, $enc, $this->mode, $iv ); + + $data = $this->unpad( $dec ); + + return $data; + } + + /** + * Encrypt the supplied data using the supplied key + * + * @param string $data The data to encrypt. + * @param string $key The key to encrypt with. + * + * @returns string The encrypted data + */ + public function encrypt( $data, $key ) { + $salt = mcrypt_create_iv( 128, MCRYPT_RAND ); + list ( $cipher_key, $mac_key, $iv ) = $this->get_keys( $salt, $key ); + + $data = $this->pad( $data ); + + $enc = mcrypt_encrypt( $this->cipher, $cipher_key, $data, $this->mode, $iv ); + + $mac = hash_hmac( 'sha512', $enc, $mac_key, true ); + return $salt . $enc . $mac; + } + + /** + * Generates a set of keys given a random salt and a master key + * + * @param string $salt A random string to change the keys each encryption. + * @param string $key The supplied key to encrypt with. + * + * @returns array An array of keys ( a cipher key, a mac key, and a IV ) + */ + protected function get_keys( $salt, $key ) { + $iv_size = mcrypt_get_iv_size( $this->cipher, $this->mode ); + $key_size = mcrypt_get_key_size( $this->cipher, $this->mode ); + $length = 2 * $key_size + $iv_size; + + $key = $this->pbkdf2( 'sha512', $key, $salt, $this->rounds, $length ); + + $cipher_key = substr( $key, 0, $key_size ); + $mac_key = substr( $key, $key_size, $key_size ); + $iv = substr( $key, 2 * $key_size ); + return array( $cipher_key, $mac_key, $iv ); + } + + /** + * Stretch the key using the PBKDF2 algorithm + * + * @see http://en.wikipedia.org/wiki/PBKDF2 + * + * @param string $algo The algorithm to use. + * @param string $key The key to stretch. + * @param string $salt A random salt. + * @param int $rounds The number of rounds to derive. + * @param int $length The length of the output key. + * + * @returns string The derived key. + */ + protected function pbkdf2( $algo, $key, $salt, $rounds, $length ) { + $size = strlen( hash( $algo, '', true ) ); + $len = ceil( $length / $size ); + $result = ''; + for ( $i = 1; $i <= $len; $i++ ) { + $tmp = hash_hmac( $algo, $salt . pack( 'N', $i ), $key, true ); + $res = $tmp; + for ( $j = 1; $j < $rounds; $j++ ) { + $tmp = hash_hmac( $algo, $tmp, $key, true ); + $res ^= $tmp; + } + $result .= $res; + } + return substr( $result, 0, $length ); + } + + /** + * Add padding based on the block size + * + * @param string $data The data to encrypt. + * @return string + */ + protected function pad( $data ) { + $length = mcrypt_get_block_size( $this->cipher, $this->mode ); + $pad_amount = $length - strlen( $data ) % $length; + if ( 0 === $pad_amount ) { + $pad_amount = $length; + } + return $data . str_repeat( chr( $pad_amount ), $pad_amount ); + } + + /** + * Remove padding based on the block size + * + * @param string $data The data to decrypt. + * @return string + */ + protected function unpad( $data ) { + $length = mcrypt_get_block_size( $this->cipher, $this->mode ); + $last = ord( $data[ strlen( $data ) - 1 ] ); + if ( $last > $length ) { + return false; + } + if ( substr( $data, -1 * $last ) !== str_repeat( chr( $last ), $last ) ) { + return false; + } + return substr( $data, 0, -1 * $last ); + } +} From 6e05b15b47d9b421b9ac41a6dab936bbc6b45c54 Mon Sep 17 00:00:00 2001 From: Jared Cobb Date: Sat, 30 Dec 2017 09:41:11 -0700 Subject: [PATCH 02/16] Finish sync logic, profile memory usage. --- admin/class-ccb-core-admin.php | 214 -------- admin/index.php | 1 - includes/class-ccb-core-admin-ajax.php | 163 ++++++ ...re-sync.php => class-ccb-core-api-old.php} | 64 ++- includes/class-ccb-core-api.php | 243 +++++++++ includes/class-ccb-core-helpers.php | 137 ++++- includes/class-ccb-core-plugin.php | 29 - includes/class-ccb-core-settings-field.php | 4 +- includes/class-ccb-core-settings-page.php | 1 + includes/class-ccb-core-settings.php | 330 +++--------- includes/class-ccb-core-synchronizer.php | 499 ++++++++++++++++++ includes/class-ccb-core.php | 67 ++- .../post-types/class-ccb-core-calendar.php | 316 ++++++++++- includes/post-types/class-ccb-core-cpt.php | 89 +--- includes/post-types/class-ccb-core-cpts.php | 400 -------------- includes/post-types/class-ccb-core-group.php | 235 ++++++++- .../class-ccb-core-calendar-event-type.php | 4 +- .../class-ccb-core-calendar-group-name.php | 4 +- .../class-ccb-core-calendar-grouping-name.php | 4 +- .../taxonomies/class-ccb-core-group-area.php | 4 +- .../taxonomies/class-ccb-core-group-day.php | 4 +- .../class-ccb-core-group-department.php | 4 +- .../taxonomies/class-ccb-core-group-tag.php | 8 +- .../taxonomies/class-ccb-core-group-time.php | 4 +- .../taxonomies/class-ccb-core-group-type.php | 4 +- .../taxonomies/class-ccb-core-taxonomy.php | 23 +- js/ccb-core-admin.js | 10 +- lib/class-ccb-core-vendor-encryption.php | 24 +- 28 files changed, 1795 insertions(+), 1094 deletions(-) delete mode 100644 admin/class-ccb-core-admin.php delete mode 100644 admin/index.php create mode 100644 includes/class-ccb-core-admin-ajax.php rename includes/{class-ccb-core-sync.php => class-ccb-core-api-old.php} (95%) create mode 100644 includes/class-ccb-core-api.php create mode 100644 includes/class-ccb-core-synchronizer.php delete mode 100644 includes/post-types/class-ccb-core-cpts.php diff --git a/admin/class-ccb-core-admin.php b/admin/class-ccb-core-admin.php deleted file mode 100644 index 6a394df..0000000 --- a/admin/class-ccb-core-admin.php +++ /dev/null @@ -1,214 +0,0 @@ - - */ -class CCB_Core_Admin extends CCB_Core_Plugin { - - /** - * Initialize the class and set its properties. - * - * @since 0.9.0 - */ - public function __construct() { - parent::__construct(); - } - - /** - * Just before the settings are saved, check for changes - * that would require us to flush the rewrite rules - * - * @param array $new_settings - * @param array $previous_settings - * @access public - * @since 0.9.6 - * @return array - */ - public function update_settings_callback( $new_settings, $previous_settings ) { - - // create a collection of settings that, if they change, should - // trigger a flush_rewrite_rules event - $setting_array = array( - 'groups-enabled', - 'groups-slug', - 'calendar-enabled', - 'calendar-slug', - ); - - foreach ( $setting_array as $setting ) { - if ( isset( $new_settings[ $setting ] ) ) { - if ( ! isset( $previous_settings[ $setting ] ) || $new_settings[ $setting ] !== $previous_settings[ $setting ] ) { - // schedule an event to flush the rewrite rules on the next page load because the settings aren't quite saved yet - wp_schedule_single_event( time(), 'schedule_flush_rewrite_rules' ); - } - } - } - - return $new_settings; - } - - /** - * Simple callback function for flushing the rewrite rules - * - * @access public - * @since 0.9.6 - * @return void - */ - public function flush_rewrite_rules_event() { - - flush_rewrite_rules(); - } - - /** - * Register the CCB custom post types if enabled - * - * @access public - * @since 0.9.0 - * @return void - */ - public function initialize_custom_post_types() { - $cpts = new CCB_Core_CPTs(); - $cpts->initialize(); - } - - /** - * Launches a synchronization from an ajax hook and will respond - * with a non-blocking ajax response - * - * @access public - * @since 0.9.0 - * @return void - */ - public function ajax_sync() { - - $nonce = $_POST['nextNonce']; - - if ( ! wp_verify_nonce( $nonce, $this->plugin_name . '-nonce' ) ) { - wp_send_json( array('success' => false) ); - } - - // tell the user to move along and go about their business... - $this->send_non_blocking_json_response( array( 'success' => true ) ); - - $sync = new CCB_Core_Sync(); - $sync->sync(); - - } - - /** - * Checks for an active synchronization from an ajax hook - * and responds with the transient value - * - * @access public - * @since 0.9.0 - * @return void - */ - public function ajax_poll_sync() { - - $nonce = $_POST['nextNonce']; - if ( ! wp_verify_nonce( $nonce, $this->plugin_name . '-nonce' ) ) { - wp_send_json( array('success' => false) ); - } - - $sync_in_progress = get_transient( $this->plugin_name . '-sync-in-progress' ); - wp_send_json( array( 'syncInProgress' => $sync_in_progress ) ); - - } - - /** - * Gets the latest synchronization results from an ajax hook - * - * @access public - * @since 0.9.0 - * @return void - */ - public function ajax_get_latest_sync() { - - $nonce = $_POST['nextNonce']; - if ( ! wp_verify_nonce( $nonce, $this->plugin_name . '-nonce' ) ) { - wp_send_json( array('success' => false) ); - } - - $latest_sync = $this->get_latest_sync_results(); - wp_send_json( $latest_sync ); - - } - - /** - * Checks the credentials for a user from an ajax hook - * - * @access public - * @since 0.9.0 - * @return void - */ - public function ajax_test_credentials() { - - $nonce = $_POST['nextNonce']; - if ( ! wp_verify_nonce( $nonce, $this->plugin_name . '-nonce' ) ) { - wp_send_json( array('success' => false) ); - } - - $sync = new CCB_Core_Sync(); - $validation_results = $sync->test_api_credentials(); - - wp_send_json( $validation_results ); - - } - - /** - * Check if we should schedule a synchronization based on - * the options set by the user - * - * @access public - * @since 0.9.0 - * @return void - */ - public function check_auto_refresh() { - - $settings = get_option( $this->plugin_settings_name ); - - if ( isset( $settings['auto-sync'] ) && $settings['auto-sync'] == 1 ) { - $latest_sync = get_option( $this->plugin_name . '-latest-sync' ); - - if ( ! empty( $latest_sync ) ) { - $auto_sync_timeout = $settings['auto-sync-timeout']; - $now = time(); - $diff = $now - $latest_sync['timestamp']; - - if ( $diff > $auto_sync_timeout * 60 ) { - wp_schedule_single_event( time(), 'schedule_auto_refresh' ); - } - - } - else { - wp_schedule_single_event( time(), 'schedule_auto_refresh' ); - } - } - } - - /** - * Callback function to kick off a synchronization - * - * @access public - * @since 0.9.0 - * @return void - */ - public function auto_sync() { - $sync = new CCB_Core_Sync(); - $sync->sync(); - } - -} diff --git a/admin/index.php b/admin/index.php deleted file mode 100644 index e71af0e..0000000 --- a/admin/index.php +++ /dev/null @@ -1 +0,0 @@ - + */ +class CCB_Core_Admin_AJAX { + + /** + * An instance of the CCB_Core_API class + * + * @var CCB_Core_API + */ + private $api; + + /** + * An instance of the CCB_Core_Synchronizer class + * + * @var CCB_Core_Synchronizer + */ + private $synchronizer; + + /** + * Initialize the class and register hooks + * + * @since 1.0.0 + */ + public function __construct() { + add_action( 'wp_ajax_sync', array( $this, 'ajax_sync' ) ); + add_action( 'wp_ajax_poll_sync', array( $this, 'ajax_poll_sync' ) ); + add_action( 'wp_ajax_test_credentials', array( $this, 'ajax_test_credentials' ) ); + add_action( 'wp_ajax_get_latest_sync', array( $this, 'ajax_get_latest_sync' ) ); + + $this->api = new CCB_Core_API(); + $this->synchronizer = new CCB_Core_Synchronizer(); + } + + /** + * Launches a synchronization from an ajax hook and will respond + * with a non-blocking ajax response + * + * @access public + * @since 1.0.0 + * @return void + */ + public function ajax_sync() { + + check_ajax_referer( 'ccb_core_nonce', 'nonce' ); + $this->synchronizer->synchronize(); + + // Tell the user to move along and go about their business... + CCB_Core_Helpers::instance()->send_non_blocking_json_success(); + + } + + /** + * Checks for an active synchronization from an ajax hook + * and responds with the transient value + * + * @access public + * @since 1.0.0 + * @return void + */ + public function ajax_poll_sync() { + + check_ajax_referer( 'ccb_core_nonce', 'nonce' ); + + //$sync_in_progress = get_transient( $this->plugin_name . '-sync-in-progress' ); + //wp_send_json( array( 'syncInProgress' => $sync_in_progress ) ); + + } + + /** + * Gets the latest synchronization results from an ajax hook + * + * @access public + * @since 1.0.0 + * @return void + */ + public function ajax_get_latest_sync() { + + check_ajax_referer( 'ccb_core_nonce', 'nonce' ); + + //$latest_sync = $this->get_latest_sync_results(); + //wp_send_json( $latest_sync ); + + } + + /** + * Checks the credentials for a user from an ajax hook + * + * @access public + * @since 1.0.0 + * @return void + */ + public function ajax_test_credentials() { + check_ajax_referer( 'ccb_core_nonce', 'nonce' ); + $response = $this->api->get( 'api_status' ); + + if ( 'SUCCESS' === $response['status'] ) { + wp_send_json_success(); + } else { + wp_send_json_error( $response['error'] ); + } + } + + /** + * Check if we should schedule a synchronization based on + * the options set by the user + * + * @access public + * @since 1.0.0 + * @return void + */ + public function check_auto_refresh() { + + $settings = get_option( $this->plugin_settings_name ); + + if ( isset( $settings['auto_sync'] ) && 1 === $settings['auto_sync'] ) { + $latest_sync = get_option( $this->plugin_name . '-latest-sync' ); + + if ( ! empty( $latest_sync ) ) { + $auto_sync_timeout = $settings['auto_sync_timeout']; + $now = time(); + $diff = $now - $latest_sync['timestamp']; + + if ( $diff > $auto_sync_timeout * 60 ) { + wp_schedule_single_event( time(), 'schedule_auto_refresh' ); + } + + } else { + wp_schedule_single_event( time(), 'schedule_auto_refresh' ); + } + } + } + + /** + * Callback function to kick off a synchronization + * + * @access public + * @since 1.0.0 + * @return void + */ + public function auto_sync() { + $sync = new CCB_Core_Sync(); + $sync->sync(); + } + +} + +new CCB_Core_Admin_AJAX(); diff --git a/includes/class-ccb-core-sync.php b/includes/class-ccb-core-api-old.php similarity index 95% rename from includes/class-ccb-core-sync.php rename to includes/class-ccb-core-api-old.php index 8d7bb99..9b880b5 100644 --- a/includes/class-ccb-core-sync.php +++ b/includes/class-ccb-core-api-old.php @@ -1,9 +1,9 @@ */ -class CCB_Core_Sync { +class CCB_Core_API { /** * The subdomain of the ccb church installation * - * @since 0.9.0 + * @since 1.0.0 * @access protected * @var string $subdomain */ @@ -32,7 +32,7 @@ class CCB_Core_Sync { /** * The ccb api username * - * @since 0.9.0 + * @since 1.0.0 * @access protected * @var string $username */ @@ -41,7 +41,7 @@ class CCB_Core_Sync { /** * The ccb api password * - * @since 0.9.0 + * @since 1.0.0 * @access protected * @var string $password */ @@ -50,7 +50,7 @@ class CCB_Core_Sync { /** * The CCB APIs we want to sync with * - * @since 0.9.0 + * @since 1.0.0 * @access protected * @var array $enabled_apis */ @@ -59,7 +59,7 @@ class CCB_Core_Sync { /** * The start date range for calendar events * - * @since 0.9.0 + * @since 1.0.0 * @access protected * @var string $calendar_start_date */ @@ -68,7 +68,7 @@ class CCB_Core_Sync { /** * The end date range for calendar events * - * @since 0.9.0 + * @since 1.0.0 * @access protected * @var string $calendar_end_date */ @@ -77,7 +77,7 @@ class CCB_Core_Sync { /** * Any valid service that the core API might integrate with * - * @since 0.9.0 + * @since 1.0.0 * @access protected * @var array $valid_services */ @@ -86,7 +86,7 @@ class CCB_Core_Sync { /** * Whether or not to additionally import group images * - * @since 0.9.5 + * @since 1.0.0 * @access protected * @var array $valid_services */ @@ -95,23 +95,21 @@ class CCB_Core_Sync { /** * Initialize the class and set its properties. * - * @since 0.9.0 + * @since 1.0.0 */ public function __construct() { - parent::__construct(); - $settings = get_option( $this->plugin_settings_name ); $this->subdomain = $settings['subdomain']; $this->username = $settings['credentials']['username']; $this->password = $this->decrypt( $settings['credentials']['password'] ); - if ( isset( $settings['groups-enabled'] ) && $settings['groups-enabled'] == 1 ) { + if ( isset( $settings['groups_enabled'] ) && $settings['groups_enabled'] == 1 ) { $this->enabled_apis['group_profiles'] = true; - if ( isset( $settings['groups-import-images'] ) && $settings['groups-import-images'] == 'yes' ) { + if ( isset( $settings['groups_import_images'] ) && $settings['groups_import_images'] == 'yes' ) { $this->import_group_images = true; } else { @@ -119,31 +117,31 @@ public function __construct() { } } - if ( isset( $settings['calendar-enabled'] ) && $settings['calendar-enabled'] == 1 ) { + if ( isset( $settings['calendar_enabled'] ) && $settings['calendar_enabled'] == 1 ) { $this->enabled_apis['public_calendar_listing'] = true; // use sane defaults if this advanced setting isn't set - if ( ! isset( $settings['calendar-date-range-type'] ) ) { + if ( ! isset( $settings['calendar_date_range_type'] ) ) { $this->calendar_start_date = date( 'Y-m-d', strtotime( '1 weeks ago') ); $this->calendar_end_date = date( 'Y-m-d', strtotime( '+16 weeks' ) ); } - elseif ( $settings['calendar-date-range-type'] == 'relative' ) { + elseif ( $settings['calendar_date_range_type'] == 'relative' ) { - $this->calendar_start_date = date( 'Y-m-d', strtotime( $settings['calendar-relative-weeks-past'] . ' weeks ago') ); - $this->calendar_end_date = date( 'Y-m-d', strtotime( '+' . $settings['calendar-relative-weeks-future'] . ' weeks' ) ); + $this->calendar_start_date = date( 'Y-m-d', strtotime( $settings['calendar_relative_weeks_past'] . ' weeks ago') ); + $this->calendar_end_date = date( 'Y-m-d', strtotime( '+' . $settings['calendar_relative_weeks_future'] . ' weeks' ) ); } - elseif ( $settings['calendar-date-range-type'] == 'specific' ) { + elseif ( $settings['calendar_date_range_type'] == 'specific' ) { // TODO: Use localization for date formats other than U.S. - if ( $settings['calendar-specific-start'] ) { + if ( $settings['calendar_specific_start'] ) { $last_year = strtotime( '1 year ago' ); - $start_timestamp = strtotime( $settings['calendar-specific-start'] ); + $start_timestamp = strtotime( $settings['calendar_specific_start'] ); if ( abs( $start_timestamp - $last_year ) > 0 ) { $this->calendar_start_date = date( 'Y-m-d', $start_timestamp ); @@ -157,10 +155,10 @@ public function __construct() { $this->calendar_start_date = date( 'Y-m-d' ); } - if ( $settings['calendar-specific-end'] ) { + if ( $settings['calendar_specific_end'] ) { $next_year = strtotime( '+1 year' ); - $end_timestamp = strtotime( $settings['calendar-specific-end'] ); + $end_timestamp = strtotime( $settings['calendar_specific_end'] ); if ( abs( $next_year - $end_timestamp ) > 0 ) { $this->calendar_end_date = date( 'Y-m-d', $end_timestamp ); @@ -220,7 +218,7 @@ public function __construct() { * ), * ) * - * @since 0.9.0 + * @since 1.0.0 * @param array $services An array of services and parameters to call * @access protected * @return void @@ -343,7 +341,7 @@ protected function call_ccb_api( $services = array() ) { * Perform a synchronization * * @access public - * @since 0.9.0 + * @since 1.0.0 * @return void */ public function sync() { @@ -448,7 +446,7 @@ public function sync() { * as defined in the constructor * * @access public - * @since 0.9.0 + * @since 1.0.0 * @return string */ public function test_api_credentials() { @@ -465,7 +463,7 @@ public function test_api_credentials() { * * @param mixed $full_response * @access protected - * @since 0.9.0 + * @since 1.0.0 * @return array */ protected function validate_response( $full_response ) { @@ -529,7 +527,7 @@ protected function validate_response( $full_response ) { * Parses the XML response, deletes existing CPTs, and imports CCB data * * @param mixed $full_response - * @since 0.9.0 + * @since 1.0.0 * @access protected * @return void */ @@ -737,7 +735,7 @@ protected function import_cpts( $full_response ) { * @param mixed $post_data * @param array $taxonomy_map * @access protected - * @since 0.9.0 + * @since 1.0.0 * @return void */ protected function get_taxonomy_atts( $post_data, $taxonomy_map ) { @@ -801,7 +799,7 @@ protected function get_taxonomy_atts( $post_data, $taxonomy_map ) { * @param array $custom_fields_map * @param string $parent_field_name * @access protected - * @since 0.9.0 + * @since 1.0.0 * @return void */ protected function get_custom_fields_atts( $post_data, $custom_fields_map, $parent_field_name = '' ) { diff --git a/includes/class-ccb-core-api.php b/includes/class-ccb-core-api.php new file mode 100644 index 0000000..18e3261 --- /dev/null +++ b/includes/class-ccb-core-api.php @@ -0,0 +1,243 @@ + + */ +class CCB_Core_API { + + /** + * Whether or not the API is ready for use + * + * @since 1.0.0 + * @access public + * @var bool $initialized + */ + public $initialized = false; + + /** + * The subdomain of the ccb church installation + * + * @since 1.0.0 + * @access protected + * @var string $subdomain + */ + protected $subdomain; + + /** + * The ccb api username + * + * @since 1.0.0 + * @access protected + * @var string $username + */ + protected $username; + + /** + * The ccb api password + * + * @since 1.0.0 + * @access protected + * @var string $password + */ + protected $password; + + /** + * Initialize the class and set its properties. + * + * @since 1.0.0 + */ + public function __construct() { + // Wait to initialize the API credentials until after WordPress + // has loaded pluggable.php because we are using some WordPress helper functions. + add_action( 'plugins_loaded', [ $this, 'initialize_credentials' ] ); + } + + /** + * Once the plugin is loaded, initialize the credentials + * + * @return void + */ + public function initialize_credentials() { + $settings = CCB_Core_Helpers::instance()->get_options(); + + if ( + ! empty( $settings['subdomain'] ) + && ! empty( $settings['credentials']['username'] ) + && ! empty( $settings['credentials']['password'] ) + ) { + $this->subdomain = $settings['subdomain']; + $this->username = $settings['credentials']['username']; + $this->password = CCB_Core_Helpers::instance()->decrypt( $settings['credentials']['password'] ); + if ( ! empty( $this->password ) && ! is_wp_error( $this->password ) ) { + $this->initialized = true; + } + } + } + + /** + * Sends a GET request to the CCB API + * + * @param string $service The service to request. + * @param array $data Optional parameters to send with the request. + * + * @since 1.0.0 + * @access public + * @return array The API response data. + */ + public function get( $service, $data = array() ) { + // Get the API response for the service. + return $this->request( 'GET', $service, $data ); + } + + /** + * Sends a POST request to the CCB API + * + * @param string $service The service to request. + * @param array $data Optional parameters to send with the request. + * + * @since 1.0.0 + * @access public + * @return array The API response data. + */ + public function post( $service, $data = array() ) { + // Get the API response for the service. + return $this->request( 'POST', $service, $data ); + } + + /** + * Executes a request against the Optimizely X API. + * + * @param string $method GET or POST. + * @param string $service The API service to execute against. + * @param array $data An optional array of data to include with the request. + * + * @since 1.0.0 + * @access private + * @return array + */ + private function request( $method, $service, $data = array() ) { + + if ( ! $this->initialized ) { + return array( + 'code' => 401, + 'error' => esc_html__( 'You are missing a subdomain, username, or password in the settings.', 'ccb-core' ), + 'status' => 'ERROR', + ); + } + + $url = esc_url_raw( sprintf( 'https://%s.ccbchurch.com/api.php?srv=%s', $this->subdomain, $service ) ); + if ( empty( $url ) ) { + return array( + 'code' => 404, + 'error' => esc_html__( 'Invalid API URL.', 'ccb-core' ), + 'status' => 'ERROR', + ); + } + + // Add authentication header to the request object. + $request = array( + 'timeout' => 60, + 'headers' => array( + 'Authorization' => 'Basic ' . base64_encode( + sprintf( + '%s:%s', + sanitize_text_field( $this->username ), + sanitize_text_field( $this->password ) + ) + ), + ), + ); + + if ( 'POST' === $method ) { + $request['body'] = $data; + } elseif ( 'GET' === $method && ! empty( $data ) ) { + $url .= '&' . http_build_query( $data ); + } + + switch ( $method ) { + case 'GET': + $response = wp_safe_remote_get( $url, $request ); + break; + case 'POST': + $response = wp_safe_remote_post( $url, $request ); + break; + default: + return array( + 'code' => 403, + 'error' => esc_html__( 'Invalid request method.', 'ccb-core' ), + 'status' => 'ERROR', + ); + } + + // Check for WordPress HTTP errors. + if ( is_wp_error( $response ) ) { + return [ + 'code' => 500, + 'error' => esc_html( $response->get_error_message() ), + 'status' => 'ERROR', + ]; + } + + // Build result object. + $result = array( + 'xml' => wp_remote_retrieve_body( $response ), + 'code' => absint( wp_remote_retrieve_response_code( $response ) ), + 'headers' => wp_remote_retrieve_headers( $response ), + ); + + // Verify there are no HTTP errors. + if ( empty( $response ) + || $result['code'] < 200 + || $result['code'] > 204 + ) { + $result['status'] = 'ERROR'; + $result['error'] = esc_html( sprintf( __( 'The API returned an empty response or an error code: %s', 'ccb-core' ), $result['code'] ) ); + return $result; + } + + try { + libxml_use_internal_errors( true ); + $parsed_response = simplexml_load_string( $result['xml'] ); + if ( false === $parsed_response ) { + $result['error'] = esc_html__( 'Could not parse the XML response', 'ccb-core' ); + $result['status'] = 'ERROR'; + } else { + + $result['body'] = $parsed_response; + + // We successfully parsed the XML response, however the + // response may contain error messages from CCB. + if ( isset( $parsed_response->response->errors->error ) ) { + $result['error'] = esc_html( sprintf( + __( 'The CCB API replied with an error: %s', 'ccb-core' ), + $parsed_response->response->errors->error + ) ); + $result['status'] = 'ERROR'; + } else { + $result['status'] = 'SUCCESS'; + } + + } + } catch ( Exception $ex ) { + $result['error'] = esc_html( sprintf( __( 'Could not parse the XML response: %s', 'ccb-core' ), $ex->getMessage() ) ); + $result['status'] = 'ERROR'; + } + + return $result; + } + +} diff --git a/includes/class-ccb-core-helpers.php b/includes/class-ccb-core-helpers.php index 5018209..1dda450 100644 --- a/includes/class-ccb-core-helpers.php +++ b/includes/class-ccb-core-helpers.php @@ -71,7 +71,7 @@ public static function instance() { */ private function setup() { // Get any options the user may have set. - $this->plugin_options = get_option( 'ccb_core' ); + $this->plugin_options = get_option( 'ccb_core_settings' ); } /** @@ -101,7 +101,7 @@ public function encrypt( $data ) { $e = new CCB_Core_Vendor_Encryption( MCRYPT_BlOWFISH, MCRYPT_MODE_CBC ); $encrypted_value = base64_encode( $e->encrypt( $data, $key ) ); } catch ( Exception $ex ) { - // TODO: Better exception handling. + return new WP_Error( 'encrypt_failure', __( 'The string could not be encrypted', 'ccb-core' ) ); } } @@ -127,7 +127,7 @@ public function decrypt( $data ) { $e = new CCB_Core_Vendor_Encryption( MCRYPT_BlOWFISH, MCRYPT_MODE_CBC ); $decrypted_value = $e->decrypt( base64_decode( $data ), $key ); } catch ( Exception $ex ) { - // TODO: Better exception handling. + return new WP_Error( 'decrypt_failure', __( 'The string could not be decrypted', 'ccb-core' ) ); } } @@ -135,4 +135,135 @@ public function decrypt( $data ) { return $decrypted_value; } + /** + * Responds to the client with a json response + * but allows the script to continue + * + * @access public + * @since 1.0.0 + * @return bool + */ + public function send_non_blocking_json_success() { + + ignore_user_abort( true ); + ob_start(); + + header( 'Content-Type: application/json' ); + header( 'Content-Encoding: none' ); + + echo wp_json_encode( [ 'success' => true ] ); + + header( 'Connection: close' ); + header( 'Content-Length: ' . ob_get_length() ); + + ob_end_flush(); + ob_flush(); + flush(); + + // Some environments may be running PHP-FPM. + if ( function_exists( 'fastcgi_finish_request' ) ) { + fastcgi_finish_request(); + } + + return true; + + } + + /** + * Downloads an image from a URL, uploads it to the Media Library, + * and then optionally attaches it to a post. + * + * We are using this custom function instead of media_sideload_image + * because images with dynamic URLs (like those on S3) do not have + * file extensions and core ticket #18730 will never be resolved. + * https://core.trac.wordpress.org/ticket/18730 + * + * @param string $image_url The URL of the image. + * @param string $filename Optional. + * @param int $post_id Optional. + * @access public + * @return mixed Returns a media id or false on failure + */ + public function download_image( $image_url, $filename = '', $post_id = 0 ) { + + // Fetch the image and store temporarily. + $temp_file = download_url( $image_url ); + + if ( is_wp_error( $temp_file ) ) { + return false; + } + + // Attempt to detect the mimetype based on the available functions. + $extension = false; + if ( function_exists( 'exif_imagetype' ) && function_exists( 'image_type_to_extension' ) ) { + // Open with exif. + $image_type = exif_imagetype( $temp_file ); + if ( $image_type ) { + $extension = image_type_to_extension( $image_type ); + } + } elseif ( function_exists( 'getimagesize' ) && function_exists( 'image_type_to_extension' ) ) { + // Open with gd. + $file_size = getimagesize( $temp_file ); + if ( isset( $file_size[2] ) ) { + $extension = image_type_to_extension( $file_size[2] ); + } + } elseif ( function_exists( 'finfo_open' ) ) { + // Open with fileinfo. + $resource = finfo_open( FILEINFO_MIME_TYPE ); + $mimetype = finfo_file( $resource, $temp_file ); + finfo_close( $resource ); + if ( $mimetype ) { + $mimetype_array = explode( '/', $mimetype ); + $extension = '.' . $mimetype_array[1]; + } + } + + // If we were able to determine the extension, move it + // to the Media Library. + if ( $extension ) { + + $filename = ! empty( $filename ) ? sanitize_file_name( $filename ) : 'ccb_' . crc32( $image_url ); + + $file_array = array( + 'name' => $filename . $extension, + 'tmp_name' => $temp_file, + ); + + add_filter( 'upload_dir', [ $this, 'custom_uploads_directory' ] ); + $media_id = media_handle_sideload( $file_array, $post_id ); + remove_filter( 'upload_dir', [ $this, 'custom_uploads_directory' ] ); + + if ( $post_id ) { + set_post_thumbnail( $post_id, $media_id ); + } + + @unlink( $temp_file ); + + if ( ! is_wp_error( $media_id ) ) { + return $media_id; + } + } + + return false; + } + + /** + * Override the default uploads directory location + * for CCB images. Allows for a convenient was to + * isolate the CCB uploads so they are not mixed in + * with other media assets. + * + * @param array $upload An array of upload paths. + * @return array + */ + public function custom_uploads_directory( $upload ) { + // Allow for the ability to enable / disable custom upload path. + if ( apply_filters( 'ccb_core_allow_custom_upload_directory', true ) ) { + $upload['path'] = trailingslashit( $upload['basedir'] ) . 'ccb'; + $upload['url'] = $upload['baseurl'] . '/ccb'; + $upload['subdir'] = '/ccb'; + } + return $upload; + } + } diff --git a/includes/class-ccb-core-plugin.php b/includes/class-ccb-core-plugin.php index 9fed1da..dbc1254 100644 --- a/includes/class-ccb-core-plugin.php +++ b/includes/class-ccb-core-plugin.php @@ -83,35 +83,6 @@ public function __construct() { } - /** - * Responds to the client with a json response - * but allows the script to continue - * - * @param array $response - * @access protected - * @since 0.9.0 - * @return bool - */ - protected function send_non_blocking_json_response( $response ) { - - ignore_user_abort(true); - ob_start(); - - header( 'Content-Type: application/json' ); - - echo json_encode( $response ); - - header( 'Connection: close' ); - header( 'Content-Length: ' . ob_get_length() ); - - ob_end_flush(); - ob_flush(); - flush(); - - return true; - - } - /** * Helper function to check if a date is valid * diff --git a/includes/class-ccb-core-settings-field.php b/includes/class-ccb-core-settings-field.php index d45a0ac..bd6ca7b 100644 --- a/includes/class-ccb-core-settings-field.php +++ b/includes/class-ccb-core-settings-field.php @@ -301,7 +301,7 @@ protected function render_manual_sync() { echo sprintf( '
- +
%4$s
', @@ -320,7 +320,7 @@ protected function render_manual_sync() { * @return void */ protected function render_latest_results() { - echo '
+ echo '
'; } diff --git a/includes/class-ccb-core-settings-page.php b/includes/class-ccb-core-settings-page.php index 3d7c01b..a3994ad 100644 --- a/includes/class-ccb-core-settings-page.php +++ b/includes/class-ccb-core-settings-page.php @@ -57,6 +57,7 @@ public function render_page() { page_id ); ?> page_id ); ?> + page_id ) { diff --git a/includes/class-ccb-core-settings.php b/includes/class-ccb-core-settings.php index af455a2..27e1b9b 100644 --- a/includes/class-ccb-core-settings.php +++ b/includes/class-ccb-core-settings.php @@ -142,7 +142,9 @@ public function validate_settings( $input ) { * @return array */ public function get_settings_definitions() { - return array( + + // Initialize a settings array with an About page and Credentials page. + $settings = [ 'ccb_core_settings' => array( 'page_title' => esc_html__( 'About', 'ccb-core' ), 'sections' => array( @@ -163,13 +165,13 @@ public function get_settings_definitions() { 'field_render_function' => 'render_text', 'field_placeholder' => 'subdomain', 'field_validation' => 'alphanumeric', - 'field_tooltip' => 'We just need the first part of your software URL (without "http://" and without ".ccbchurch.com").', + 'field_tooltip' => esc_html__( 'We just need the first part of your software URL (without "http://" and without ".ccbchurch.com").', 'ccb-core' ), ), 'credentials' => array( 'field_title' => esc_html__( 'API Credentials', 'ccb-core' ), 'field_render_function' => 'render_credentials', 'field_validation' => 'encrypt', - 'field_tooltip' => 'This is the username and password for the API user in your Church Community Builder software.', + 'field_tooltip' => esc_html__( 'This is the username and password for the API user in your Church Community Builder software.', 'ccb-core' ), ), 'test_credentials' => array( 'field_title' => esc_html__( 'Test Credentials', 'ccb-core' ), @@ -179,279 +181,89 @@ public function get_settings_definitions() { ), ), ), - 'ccb_core_settings_groups' => array( - 'page_title' => esc_html__( 'Groups', 'ccb-core' ), - 'sections' => array( - 'groups' => array( - 'section_title' => esc_html__( 'Groups', 'ccb-core' ), - 'fields' => array( - 'groups-enabled' => array( - 'field_title' => esc_html__( 'Enable Groups', 'ccb-core' ), - 'field_render_function' => 'render_switch', - 'field_validation' => 'switch', - ), - 'groups-name' => array( - 'field_title' => esc_html__( 'Groups Display Name', 'ccb-core' ), - 'field_render_function' => 'render_text', - 'field_placeholder' => esc_html__( 'Groups', 'ccb-core' ), - 'field_validation' => 'alphanumeric_extended', - 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1}' ), - 'field_tooltip' => 'This is what you call the groups in your church (i.e. Home Groups, Connections, Life Groups, etc.).', - ), - 'groups-slug' => array( - 'field_title' => esc_html__( 'Groups URL Name', 'ccb-core' ), - 'field_render_function' => 'render_text', - 'field_placeholder' => 'groups', - 'field_validation' => 'slug', - 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1}' ), - 'field_tooltip' => 'This is typically where your theme will display all the groups. WordPress calls this a "slug".', - ), - 'groups-import-images' => array( - 'field_title' => esc_html__( 'Also Import Group Images?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'no', - 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1}' ), - 'field_tooltip' => 'This will download the CCB Group Image and attach it as a Featured Image.
If you don\'t need group images, then disabling this feature will speed up the synchronization.', - ), - 'groups-advanced' => array( - 'field_title' => esc_html__( 'Enable Advanced Settings (Optional)', 'ccb-core' ), - 'field_render_function' => 'render_switch', - 'field_validation' => 'switch', - 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1}' ), - ), - 'groups-exclude-from-search' => array( - 'field_title' => esc_html__( 'Exclude From Search?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'no', - 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1,"groups-advanced":1}' ), - ), - 'groups-publicly-queryable' => array( - 'field_title' => esc_html__( 'Publicly Queryable?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'yes', - 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1,"groups-advanced":1}' ), - ), - 'groups-show-ui' => array( - 'field_title' => esc_html__( 'Show In Admin UI?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'yes', - 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1,"groups-advanced":1}' ), - ), - 'groups-show-in-nav-menus' => array( - 'field_title' => esc_html__( 'Show In Navigation Menus?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'no', - 'field_attributes' => array( 'data-requires' => '{"groups-enabled":1,"groups-advanced":1}' ), - ), + ]; + + /** + * Allow custom post types to have their own settings pages. + * + * If you implement a custom post type and do not want to + * expose a settings page, simply `return $settings` from + * within your implementation of `get_post_settings_definitions()` + * + * @since 1.0.0 + * + * @param array $settings The current array of settings definitions. + */ + $settings = apply_filters( 'ccb_core_post_type_settings_definitions', $settings ); + + // Add a syncronization settings page. + $settings['ccb_core_settings_sync'] = array( + 'page_title' => esc_html__( 'Synchronize', 'ccb-core' ), + 'sections' => array( + 'synchronize' => array( + 'section_title' => esc_html__( 'Synchronize', 'ccb-core' ), + 'fields' => array( + 'auto_sync' => array( + 'field_title' => esc_html__( 'Enable Auto Sync', 'ccb-core' ), + 'field_render_function' => 'render_switch', + 'field_validation' => 'switch', ), - ), - ), - ), - 'ccb_core_settings_calendar' => array( - 'page_title' => esc_html__( 'Public Events', 'ccb-core' ), - 'sections' => array( - 'calendar' => array( - 'section_title' => esc_html__( 'Public Events', 'ccb-core' ), - 'fields' => array( - 'calendar-enabled' => array( - 'field_title' => esc_html__( 'Enable Events', 'ccb-core' ), - 'field_render_function' => 'render_switch', - 'field_validation' => 'switch', + 'auto_sync_timeout' => array( + 'field_title' => esc_html__( 'Cache Expiration', 'ccb-core' ), + 'field_render_function' => 'render_slider', + 'field_options' => array( + 'min' => '10', + 'max' => '180', + 'units' => 'minutes', ), - 'calendar-name' => array( - 'field_title' => esc_html__( 'Event Display Name', 'ccb-core' ), - 'field_render_function' => 'render_text', - 'field_placeholder' => esc_html__( 'Events', 'ccb-core' ), - 'field_validation' => 'alphanumeric_extended', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1}' ), - 'field_tooltip' => 'This is what you call the events in your church (i.e. Meetups, Hangouts, etc.).', - ), - 'calendar-slug' => array( - 'field_title' => esc_html__( 'Events URL Name', 'ccb-core' ), - 'field_render_function' => 'render_text', - 'field_placeholder' => 'events', - 'field_validation' => 'slug', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1}' ), - 'field_tooltip' => 'This is typically where your theme will display all the events. WordPress calls this a "slug".', - ), - 'calendar-advanced' => array( - 'field_title' => esc_html__( 'Enable Advanced Settings (Optional)', 'ccb-core' ), - 'field_render_function' => 'render_switch', - 'field_validation' => 'switch', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1}' ), - ), - 'calendar-date-range-type' => array( - 'field_title' => esc_html__( 'Date Range Type', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'relative' => esc_html__( 'Relative Range', 'ccb-core' ), - 'specific' => esc_html__( 'Specific Range', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'relative', - 'field_attributes' => array( 'class' => 'date-range-type', 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1}' ), - 'field_tooltip' => 'Relative: For example, always get the events from \'One week ago\', up to \'Eight weeks from now\'.
This is the best setting for most churches.

Specific: For example, only get events from \'6/1/2015\' to \'12/1/2015\'.
This setting is best if you want to tightly manage the events that get published.', - ), - 'calendar-relative-weeks-past' => array( - 'field_title' => esc_html__( 'How Far Back?', 'ccb-core' ), - 'field_render_function' => 'render_slider', - 'field_options' => array( - 'min' => '0', - 'max' => '26', - 'units' => 'weeks', - ), - 'field_default' => 1, - 'field_validation' => '', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1,"calendar-date-range-type":"relative"}' ), - 'field_tooltip' => 'Every time we synchronize, how many weeks in the past should we look?(0 would be "today")', - ), - 'calendar-relative-weeks-future' => array( - 'field_title' => esc_html__( 'How Into The Future?', 'ccb-core' ), - 'field_render_function' => 'render_slider', - 'field_options' => array( - 'min' => '1', - 'max' => '52', - 'units' => 'weeks', - ), - 'field_default' => 16, - 'field_validation' => '', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1,"calendar-date-range-type":"relative"}' ), - 'field_tooltip' => 'Every time we synchronize, how many weeks in the future should we look?', - ), - 'calendar-specific-start' => array( - 'field_title' => esc_html__( 'Specific Start Date', 'ccb-core' ), - 'field_render_function' => 'render_date_picker', - 'field_validation' => '', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1,"calendar-date-range-type":"specific"}' ), - 'field_tooltip' => 'When synchronizing, we should get events that start after this date.
(Leave empty to always start "today")', - ), - 'calendar-specific-end' => array( - 'field_title' => esc_html__( 'Specific End Date', 'ccb-core' ), - 'field_render_function' => 'render_date_picker', - 'field_validation' => '', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1,"calendar-date-range-type":"specific"}' ), - 'field_tooltip' => 'When synchronizing, we should get events that start before this date.
(Setting this too far into the future may cause the API to timeout)', - ), - 'calendar-exclude-from-search' => array( - 'field_title' => esc_html__( 'Exclude From Search?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'no', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1}' ), - ), - 'calendar-publicly-queryable' => array( - 'field_title' => esc_html__( 'Publicly Queryable?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'yes', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1}' ), - ), - 'calendar-show-ui' => array( - 'field_title' => esc_html__( 'Show In Admin UI?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'yes', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1}' ), - ), - 'calendar-show-in-nav-menus' => array( - 'field_title' => esc_html__( 'Show In Navigation Menus?', 'ccb-core' ), - 'field_render_function' => 'render_radio', - 'field_options' => array( - 'yes' => esc_html__( 'Yes', 'ccb-core' ), - 'no' => esc_html__( 'No', 'ccb-core' ), - ), - 'field_validation' => '', - 'field_default' => 'no', - 'field_attributes' => array( 'data-requires' => '{"calendar-enabled":1,"calendar-advanced":1}' ), + 'field_default' => 90, + 'field_validation' => '', + 'field_attributes' => array( 'data-requires' => '{"auto_sync":1}' ), + 'field_tooltip' => sprintf( + esc_html__( + 'We keep a local copy (cache) of your Church Community Builder data for the best performance.%1$s + How often (in minutes) should we check for new data?%2$s + (90 minutes is recommended).', + 'ccb-core' ), + '
', + '
' ), ), - ), - ), - ), - 'ccb_core_settings_sync' => array( - 'page_title' => esc_html__( 'Synchronize', 'ccb-core' ), - 'sections' => array( - 'synchronize' => array( - 'section_title' => esc_html__( 'Synchronize', 'ccb-core' ), - 'fields' => array( - 'auto-sync' => array( - 'field_title' => esc_html__( 'Enable Auto Sync', 'ccb-core' ), - 'field_render_function' => 'render_switch', - 'field_validation' => 'switch', - ), - 'auto-sync-timeout' => array( - 'field_title' => esc_html__( 'Cache Expiration', 'ccb-core' ), - 'field_render_function' => 'render_slider', - 'field_options' => array( - 'min' => '10', - 'max' => '180', - 'units' => 'minutes', - ), - 'field_default' => 90, - 'field_validation' => '', - 'field_attributes' => array( 'data-requires' => '{"auto-sync":1}' ), - 'field_tooltip' => 'We keep a local copy (cache) of your Church Community Builder data for the best performance.
How often (in minutes) should we check for new data?
90 minutes is recommended.', - ), - 'manual-sync' => array( - 'field_title' => esc_html__( 'Manual Sync', 'ccb-core' ), - 'field_render_function' => 'render_manual_sync', - ), - 'latest-results' => array( - 'field_title' => esc_html__( 'Latest Sync Results', 'ccb-core' ), - 'field_render_function' => 'render_latest_results', - ), + 'manual_sync' => array( + 'field_title' => esc_html__( 'Manual Sync', 'ccb-core' ), + 'field_render_function' => 'render_manual_sync', + ), + 'latest_results' => array( + 'field_title' => esc_html__( 'Latest Sync Results', 'ccb-core' ), + 'field_render_function' => 'render_latest_results', ), ), ), ), ); + + /** + * Allow filtering of the entire settings array. + * + * @since 1.0.0 + * + * @param array $settings The current array of settings definitions. + */ + return apply_filters( 'ccb_core_settings_definitions', $settings ); + } /** * Helper function to create a name/value hash for quick validation * - * @access protected + * @access private * @since 0.9.0 * @return array $mapping */ - protected function generate_validation_hash() { + private function generate_validation_hash() { + // Verify the nonce before processing field data. + check_admin_referer( 'update_settings', 'ccb_core_nonce' ); + $mapping = array(); $page_id = isset( $_POST['option_page'] ) ? sanitize_text_field( wp_unslash( $_POST['option_page'] ) ) : false; // Input var okay. $settings_definitions = $this->get_settings_definitions(); diff --git a/includes/class-ccb-core-synchronizer.php b/includes/class-ccb-core-synchronizer.php new file mode 100644 index 0000000..180d9dd --- /dev/null +++ b/includes/class-ccb-core-synchronizer.php @@ -0,0 +1,499 @@ + + */ +class CCB_Core_Synchronizer { + + /** + * A complete mapping of CCB API data to post types and taxonomies + * + * @var array + */ + private $map; + + /** + * An instance of the CCB_Core_API class + * + * @var CCB_Core_API + */ + private $api; + + /** + * Initialize the class and set its properties. + * + * @since 1.0.0 + */ + public function __construct() { + // Wait to initialize the map until after the plugins / themes are fully + // loaded so that all post types and taxonomies have been registered. + add_action( 'init', [ $this, 'initialize_map' ] ); + + $this->api = new CCB_Core_API(); + } + + /** + * Setup the registered post type maps and taxonomy maps + * from the custom post type and taxonomy classes. + * + * @return void + */ + public function initialize_map() { + $post_type_maps = []; + $taxonomy_maps = []; + $this->map = array_merge_recursive( + apply_filters( 'ccb_core_post_type_map', $post_type_maps ), + apply_filters( 'ccb_core_taxonomy_map', $taxonomy_maps ) + ); + } + + /** + * Calls the CCB API and synchronizes post objects and + * taxonomies based on the mapping definitions from the + * custom post type and custom taxonomy classes. + * + * @return bool True on success + */ + public function synchronize() { + + // For each registered custom post type, call the API and + // get a response object. + foreach ( $this->map as $post_type => $settings ) { + if ( ! empty( $settings['service'] ) ) { + $data = ! empty( $settings['data'] ) ? $settings['data'] : []; + $response = $this->api->get( $settings['service'], $data ); + if ( 'SUCCESS' === $response['status'] ) { + $update_successful = $this->update_content( $post_type, $settings, $response ); + } + } + } + + return true; + } + + /** + * Takes an API response and will either Insert, Update, or Delete + * content based on the settings and any applicable existing content. + * + * @param string $post_type The post type being updated. + * @param array $settings The settings for the mapping. + * @param array $response An API response. + * @return void + */ + private function update_content( $post_type, $settings, $response ) { + + // The nodes are mapped down from the parent(s) to the + // single child object. Get a collection of entities + // that will map to a single post type. + $entities = $this->get_entities( $response, $settings['nodes'] ); + + // Get a collection of existing posts (previously imported) from WordPress. + // This is organized by entitiy id (from CCB) and contains the WordPress + // post_id and optional modified date. + $post_data = $this->get_existing_post_data( $post_type ); + + // Organize the entities and existing post data into their + // respective CRUD operations. This will return an array + // with entities to insert for the first time, entities + // to update (that already exist and have changed), and + // posts that no longer exist in CCB and should be deleted. + $organized_entities = $this->organize_entities( $entities, $post_data, $post_type ); + + error_log( 'before insert ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + if ( ! empty( $organized_entities['insert_update'] ) ) { + $insert_result = $this->insert_update_entities( $organized_entities['insert_update'], $post_type, $settings ); + } + error_log( 'after insert ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + error_log( 'before delete ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + if ( ! empty( $organized_entities['delete'] ) ) { + $delete_result = $this->delete_posts( $organized_entities['delete'] ); + } + error_log( 'after delete ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + + $this->delete_empty_terms( $settings ); + + } + + private function get_entities( $response, $nodes ) { + if ( ! empty( $nodes ) ) { + $depth = count( $nodes ) - 1; + $collection = $response['body']->response; + for ( $i = 0; $i < $depth; $i++ ) { + $collection = $collection->{$nodes[ $i ]}; + } + return $collection; + } + return false; + } + + private function get_existing_post_data( $post_type ) { + // Batch the WP_Query for performance. + $posts_per_page = 100; + $offset = 0; + $collection = []; + + do { + $args = [ + 'post_type' => $post_type, + 'post_status' => 'any', + 'posts_per_page' => $posts_per_page, + 'offset' => $offset, + 'no_rows_found' => true, + 'fields' => 'ids', + ]; + + $posts = new WP_Query( $args ); + + if ( ! empty( $posts->posts ) ) { + foreach ( $posts->posts as $post_id ) { + + // These are saved during the insert / update process (of possible) + // in order to attempt future updates. + $entity_id = get_post_meta( $post_id, 'entity_id', true ); + $ccb_modified_date = get_post_meta( $post_id, 'ccb_modified_date', true ); + + if ( ! empty( $entity_id ) ) { + $collection[ $entity_id ] = [ + 'post_id' => $post_id, + 'ccb_modified_date' => $ccb_modified_date, + ]; + } + } + } + + $offset = $offset + $posts_per_page; + + } while ( count( $posts->posts ) ); + + return $collection; + } + + private function organize_entities( $entities, $post_data, $post_type ) { + + $collection = [ + 'insert_update' => [], + 'delete' => [], + ]; + + // Create a master collection of new entity ids + // that were either inserted or updated + // for quick filtering of existing posts + // so that we can find posts to delete. + $synced_entity_ids = []; + + error_log( 'before organize ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + foreach ( $entities->children() as $entity ) { + + $entity_id = $this->get_entity_id( $entity ); + + // If the entity_id is a 32 character hash it means we cannot + // map it to some known older post data because it either + // doesn't have a CCB ID or else it doesn't give us a modified + // date. So we hash its string value in order to get a unique + // signature. We use this to determine if we "already have this + // thing" so we don't need to insert / update it. + if ( 32 === strlen( $entity_id ) ) { + if ( apply_filters( 'ccb_core_entity_insert_allowed', true, $entity_id, $entity, $post_type ) ) { + // If the unique id for the new entity couldn't be found + // in the existing collection, it is new. Add it + // to the insert collection. + if ( ! array_key_exists( $entity_id, $post_data ) ) { + $entity->addChild( 'post_id', 0 ); // This is a WordPress post ID, so this will be an insert. + $collection['insert_update'][] = $entity; + } + // Regardless of whether or not this unique entity exists + // (i.e. regardless of an insert or update) we still record + // that it was synced so that it doesn't get deleted. + $synced_entity_ids[] = $entity_id; + } + } else { + if ( array_key_exists( $entity_id, $post_data ) && ! empty( $entity->modified ) ) { + if ( apply_filters( 'ccb_core_entity_update_allowed', true, $entity_id, $entity, $post_type ) ) { + // If an existing post has a newer modified date from the API + // add it to the insert_update collection with its existing post id. + if ( strtotime( $entity->modified ) > strtotime( $post_data[ $entity_id ]['ccb_modified_date'] ) ) { + $entity->addChild( 'post_id', $post_data[ $entity_id ]['post_id'] ); // This is a WordPress post ID, so this will be an update. + $collection['insert_update'][] = $entity; + } + // Even though we may not have made an update + // we still add this as a "synced" id because it exists. + $synced_entity_ids[] = $entity_id; + } + } else { + // The unique id for the new entity couldn't be found + // in the existing collection, so it is new. Add it + // to the insert_update collection. + if ( apply_filters( 'ccb_core_entity_insert_allowed', true, $entity_id, $entity, $post_type ) ) { + $entity->addChild( 'post_id', 0 ); // This is a WordPress post ID, so this will be an insert. + $collection['insert_update'][] = $entity; + $synced_entity_ids[] = $entity_id; + } + } + } + + } + + error_log( 'after organize ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + // For each existing post, check to see if it was included + // in the new entity id collection from this most recent + // API response. If it doesn't exist, it was deleted. + foreach ( $post_data as $entity_id => $data ) { + if ( ! in_array( $entity_id, $synced_entity_ids, true ) ) { + if ( apply_filters( 'ccb_core_entity_delete_allowed', true, $entity_id, $data, $post_type ) ) { + $collection['delete'][] = $data['post_id']; + } + } + } + + return $collection; + } + + private function get_entity_id( $entity ) { + // If an entity doesn't have a CCB ID, we cannot match it during + // the sync process in order to perform an update. So instead, + // create a hash of the entity and store it as a unique identifier. + // + // If an entity has an actual CCB ID, but *doesn't* have a modified + // date, there's no point in storing the CCB ID. This is because + // without a modified date we cannot determine if the entity has + // changed since the previous sync. Instead, just create a has of the entity. + if ( ! empty( $entity->attributes() ) && ! empty( $entity->modified ) ) { + foreach ( $entity->attributes() as $key => $value ) { + if ( false !== stristr( $key, 'id' ) ) { + return (int) $value; + } + } + } + if ( isset( $entity->post_id ) ) { + unset( $entity->post_id ); + } + return md5( $entity->asXML() ); + } + + private function insert_update_entities( $entities, $post_type, $settings ) { + global $wpdb; + // Temporarily disable counting for performance. + wp_defer_term_counting( true ); + wp_defer_comment_counting( true ); + wp_suspend_cache_addition( true ); + remove_action( 'do_pings', 'do_all_pings', 10, 1 ); + + error_log( 'after setting optimizations ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + + // Temporarily disable autocommit. + $wpdb->query( 'SET autocommit = 0;' ); // db call ok; no cache ok. + set_time_limit( 600 ); + + foreach ( $entities as $entity ) { + // Build a new $args array for each post insert + // based on the settings config. + $args = [ + 'ID' => (int) $entity->post_id, // Will be set to 0 if this is an insert. + 'post_title' => '', // Default to empty, expected to be overriden by the settings. + 'post_content' => '', // Default to empty, expected to be overriden by the settings. + 'post_status' => 'publish', + 'post_type' => $post_type, + 'meta_input' => [], + 'tax_input' => [], + ]; + + // Inspect each field defined in the settings. If it's a + // post_meta element, add it to the meta_input array, otherwise + // assume it's part of the parent post object. + foreach ( $settings['fields'] as $element => $field ) { + if ( 'post_meta' === $field ) { + $args['meta_input'][ $element ] = $this->auto_cast( $entity->{$element} ); + } else { + $args[ $field ] = $this->auto_cast( $entity->{$element} ); + } + } + + // Attempt to store additional meta related to synchronization. + // If CCB provided a CCB ID for this entitiy, save it. If CCB + // provided a `modified` node, save it. We use them to attempt + // updates to the entity when needed. + $args['meta_input']['entity_id'] = $this->get_entity_id( $entity ); + if ( isset( $entity->modified ) ) { + $args['meta_input']['ccb_modified_date'] = $this->auto_cast( $entity->modified ); + } + + // Prepare hierarchial taxonomies by ensuring the term id + // already exists and set terms ids. + $prepared_terms = $this->prepare_terms( $entity, $settings ); + if ( ! empty( $prepared_terms ) ) { + $args['tax_input'] = $prepared_terms; + } + + // If this is an update, we need to remove all term + // relationships in order to ensure we are in + // sync with the most recent entity. Otherwise if a + // relationship was removed by CCB, it'll persist in WordPress. + if ( $args['ID'] ) { + if ( ! empty( $settings['taxonomies']['hierarchical'] ) ) { + foreach ( $settings['taxonomies']['hierarchical'] as $taxonomy => $node ) { + wp_delete_object_term_relationships( $args['ID'], $taxonomy ); + } + } + if ( ! empty( $settings['taxonomies']['nonhierarchical'] ) ) { + foreach ( $settings['taxonomies']['nonhierarchical'] as $taxonomy => $node ) { + wp_delete_object_term_relationships( $args['ID'], $taxonomy ); + } + } + } + + do_action( 'ccb_core_before_insert_update_post', $entity, $settings, $args, $post_type ); + + // Perform an insert (or update if we included a post id). + $post_id = wp_insert_post( $args, true ); + + do_action( 'ccb_core_after_insert_update_post', $entity, $settings, $args, $post_type, $post_id ); + } + + error_log( 'after loop, before commit ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + + // Commit the database operations now. + $wpdb->query( 'COMMIT;' ); // db call ok; no cache ok. + // Re-enable autocommit. + $wpdb->query( 'SET autocommit = 1;' ); // db call ok; no cache ok. + + error_log( 'after loop, before stop_the_insanity ' . $post_type . ': ' . size_format( memory_get_usage() ) ); + + // Re-enable counting. + wp_suspend_cache_addition( false ); + wp_defer_term_counting( false ); + wp_defer_comment_counting( false ); + $this->stop_the_insanity(); + } + + private function delete_posts( $post_ids ) { + foreach ( $post_ids as $post_id ) { + wp_delete_post( $post_id, true ); + } + return true; + } + + private function delete_empty_terms( $settings ) { + $args = [ + 'hide_empty' => false, + ]; + + if ( ! empty( $settings['taxonomies']['hierarchical'] ) ) { + foreach ( $settings['taxonomies']['hierarchical'] as $taxonomy => $node ) { + $args['taxonomy'][] = $taxonomy; + } + } + if ( ! empty( $settings['taxonomies']['nonhierarchical'] ) ) { + foreach ( $settings['taxonomies']['nonhierarchical'] as $taxonomy => $node ) { + $args['taxonomy'][] = $taxonomy; + } + } + + $terms_query = new WP_Term_Query( $args ); + foreach ( $terms_query->get_terms() as $term ) { + if ( 0 === $term->count ) { + wp_delete_term( $term->term_id, $term->taxonomy ); + } + } + + return true; + } + + private function prepare_terms( $entity, $settings ) { + $categories = []; + $tags = []; + + if ( ! empty( $settings['taxonomies']['hierarchical'] ) ) { + foreach ( $settings['taxonomies']['hierarchical'] as $taxonomy => $node ) { + $term_name = $this->auto_cast( $entity->{$node} ); + if ( $term_name ) { + $term = term_exists( $term_name, $taxonomy ); + if ( ! $term ) { + $term = wp_insert_term( $term_name, $taxonomy ); + } + if ( $term && ! is_wp_error( $term ) ) { + $categories[ $taxonomy ][] = $term['term_taxonomy_id']; + } + } + } + } + + if ( ! empty( $settings['taxonomies']['nonhierarchical'] ) ) { + foreach ( $settings['taxonomies']['nonhierarchical'] as $taxonomy => $node_array ) { + foreach ( $node_array as $node => $tag ) { + $tag_is_set = $this->auto_cast( $entity->{$node} ); + if ( $tag_is_set ) { + $tags[ $taxonomy ][] = $tag; + } + } + } + } + + return array_merge( $categories, $tags ); + } + + private function auto_cast( $element ) { + if ( $element->children()->count() ) { + $array = []; + // If the node happens to have an id attribute, + // transform it into a property so that it's not lost. + $id = false; + if ( ! empty( $element->attributes() ) ) { + foreach ( $element->attributes() as $key => $value ) { + if ( false !== stristr( $key, 'id' ) ) { + $id = (int) $value; + break; + } + } + } + if ( $id ) { + $array['id'] = $id; + } + foreach ( $element->children() as $child ) { + $array[ $child->getName() ] = $this->auto_cast( $child ); + } + return $array; + } elseif ( 'true' === (string) $element ) { + return true; + } elseif ( 'false' === (string) $element ) { + return false; + } elseif ( strlen( (int) $element ) === strlen( (string) $element ) ) { + return (int) $element; + } else { + return (string) $element; + } + } + + /** + * Clear all of the caches for memory management + */ + private function stop_the_insanity() { + global $wpdb, $wp_object_cache; + + $wpdb->queries = array(); + + if ( is_object( $wp_object_cache ) ) { + $wp_object_cache->group_ops = array(); + $wp_object_cache->stats = array(); + $wp_object_cache->memcache_debug = array(); + $wp_object_cache->cache = array(); + + if ( method_exists( $wp_object_cache, '__remoteset' ) ) { + $wp_object_cache->__remoteset(); + } + } + } +} diff --git a/includes/class-ccb-core.php b/includes/class-ccb-core.php index d52fa45..5248e9f 100644 --- a/includes/class-ccb-core.php +++ b/includes/class-ccb-core.php @@ -59,8 +59,11 @@ private function load_dependencies() { require_once CCB_CORE_PATH . 'includes/class-ccb-core-settings-section.php'; require_once CCB_CORE_PATH . 'includes/class-ccb-core-settings-field.php'; - // The class that handles data synchronization between CCB and the local cache. - require_once CCB_CORE_PATH . 'includes/class-ccb-core-sync.php'; + // The class that handles communication with the CCB API. + require_once CCB_CORE_PATH . 'includes/class-ccb-core-api.php'; + + // The class that handles synchronization logic. + require_once CCB_CORE_PATH . 'includes/class-ccb-core-synchronizer.php'; // Custom Post Type classes. require_once CCB_CORE_PATH . 'includes/post-types/class-ccb-core-cpt.php'; @@ -79,6 +82,9 @@ private function load_dependencies() { require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-group-time.php'; require_once CCB_CORE_PATH . 'includes/taxonomies/class-ccb-core-group-type.php'; + // Admin AJAX methods. + require_once CCB_CORE_PATH . 'includes/class-ccb-core-admin-ajax.php'; + } /** @@ -100,23 +106,12 @@ private function define_hooks() { add_action( 'admin_menu', array( $this, 'initialize_settings_menu' ) ); add_action( 'admin_init', array( $this, 'initialize_settings' ) ); + // Callback for after the options are saved. + add_action( 'update_option_ccb_core_settings', array( $this, 'updated_options' ), 10, 2 ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); - /*// Cron related hooks. - $this->loader->add_action( 'schedule_auto_refresh', $plugin_admin, 'auto_sync' ); - $this->loader->add_action( 'wp_loaded', $plugin_admin, 'check_auto_refresh' ); - - // User initiated actions. - $this->loader->add_action( 'pre_update_option_' . $this->plugin_settings_name, $plugin_admin, 'update_settings_callback', 10, 2 ); - $this->loader->add_action( 'schedule_flush_rewrite_rules', $plugin_admin, 'flush_rewrite_rules_event' ); - - // All backend ajax hooks. - $this->loader->add_action( 'wp_ajax_sync', $plugin_admin, 'ajax_sync' ); - $this->loader->add_action( 'wp_ajax_poll_sync', $plugin_admin, 'ajax_poll_sync' ); - $this->loader->add_action( 'wp_ajax_test_credentials', $plugin_admin, 'ajax_test_credentials' ); - $this->loader->add_action( 'wp_ajax_get_latest_sync', $plugin_admin, 'ajax_get_latest_sync' );*/ - } /** @@ -237,6 +232,39 @@ public function initialize_settings() { } + /** + * After the options are saved, check to see if we + * should flush the rewrite rules. + * + * @param array $old_value The previous option value. + * @param array $value The new option value. + * @access public + * @since 1.0.0 + * @return void + */ + public function updated_options( $old_value, $value ) { + + // Create a collection of settings that, if they change, should + // trigger a flush_rewrite_rules event. + $setting_array = array( + 'groups_enabled', + 'groups_slug', + 'calendar_enabled', + 'calendar_slug', + ); + + foreach ( $setting_array as $setting ) { + if ( isset( $value[ $setting ] ) ) { + if ( ! isset( $old_value[ $setting ] ) || $value[ $setting ] !== $old_value[ $setting ] ) { + // At least one option requires a flush, so do it once and return. + flush_rewrite_rules(); + return; + } + } + } + + } + /** * Register the stylesheets for the dashboard. * @@ -271,8 +299,11 @@ public function enqueue_scripts( $hook ) { wp_enqueue_script( 'picker', CCB_CORE_URL . 'js/vendor/picker.js', array( 'jquery' ), CCB_CORE_VERSION, false ); wp_enqueue_script( 'picker-date', CCB_CORE_URL . 'js/vendor/picker.date.js', array( 'picker' ), CCB_CORE_VERSION, false ); wp_enqueue_script( 'tipr', CCB_CORE_URL . 'js/vendor/tipr.min.js', array( 'jquery' ), CCB_CORE_VERSION, false ); - wp_localize_script( 'ccb-core', 'CCB_CORE_SETTINGS', array( - 'nextNonce' => wp_create_nonce( 'ccb-core-nonce' ), + wp_localize_script( + 'ccb-core', + 'CCB_CORE_SETTINGS', + array( + 'nonce' => wp_create_nonce( 'ccb_core_nonce' ), ) ); } diff --git a/includes/post-types/class-ccb-core-calendar.php b/includes/post-types/class-ccb-core-calendar.php index ece256a..895b0ca 100644 --- a/includes/post-types/class-ccb-core-calendar.php +++ b/includes/post-types/class-ccb-core-calendar.php @@ -23,46 +23,324 @@ class CCB_Core_Calendar extends CCB_Core_CPT { * * @var string */ - public $name = 'ccb-core-calendar'; + public $name = 'ccb_core_calendar'; /** - * Setup the default CPT options + * Initialize the class + */ + public function __construct() { + $options = CCB_Core_Helpers::instance()->get_options(); + $this->enabled = ! empty( $options['calendar_enabled'] ) ? true : false; + parent::__construct(); + } + + /** + * Setup the custom post type args * * @since 1.0.0 - * @return array Default options for register_post_type + * @return array $args for register_post_type */ - public function get_cpt_defaults() { + public function get_post_args() { + + $options = CCB_Core_Helpers::instance()->get_options(); + $plural = ! empty( $options['calendar_name'] ) ? $options['calendar_name'] : __( 'Events', 'ccb-core' ); + $singular = ! empty( $options['calendar_name_singular'] ) ? $options['calendar_name_singular'] : __( 'Event', 'ccb-core' ); + $rewrite = ! empty( $options['calendar_slug'] ) ? [ 'slug' => sanitize_title( $options['calendar_slug'] ) ] : [ 'slug' => 'events' ]; + $has_archive = ! empty( $options['calendar_slug'] ) ? sanitize_title( $options['calendar_slug'] ) : 'events'; + $exclude_from_search = ! empty( $options['calendar_exclude_from_search'] ) && 'yes' === $options['calendar_exclude_from_search'] ? true : false; + $publicly_queryable = ! empty( $options['calendar_publicly_queryable'] ) && 'no' === $options['calendar_publicly_queryable'] ? false : true; + $show_ui = ! empty( $options['calendar_show_ui'] ) && 'no' === $options['calendar_show_ui'] ? false : true; + $show_in_nav_menus = ! empty( $options['calendar_show_in_nav_menus'] ) && 'yes' === $options['calendar_show_in_nav_menus'] ? true : false; + return array( 'labels' => array( - 'name' => __( 'Events', 'ccb-core' ), - 'singular_name' => __( 'Event', 'ccb-core' ), - 'all_items' => __( 'All Events', 'ccb-core' ), + 'name' => $plural, + 'singular_name' => $singular, + 'all_items' => sprintf( __( 'All %s', 'ccb-core' ), $plural ), 'add_new' => __( 'Add New', 'ccb-core' ), - 'add_new_item' => __( 'Add New Event', 'ccb-core' ), + 'add_new_item' => sprintf( __( 'Add New %s', 'ccb-core' ), $singular ), 'edit' => __( 'Edit', 'ccb-core' ), - 'edit_item' => __( 'Edit Event', 'ccb-core' ), - 'new_item' => __( 'New Event', 'ccb-core' ), - 'view_item' => __( 'View Event', 'ccb-core' ), - 'search_items' => __( 'Search Events', 'ccb-core' ), + 'edit_item' => sprintf( __( 'Edit %s', 'ccb-core' ), $singular ), + 'new_item' => sprintf( __( 'New %s', 'ccb-core' ), $singular ), + 'view_item' => sprintf( __( 'View %s', 'ccb-core' ), $singular ), + 'search_items' => sprintf( __( 'Search %s', 'ccb-core' ), $plural ), 'not_found' => __( 'Nothing found in the Database.', 'ccb-core' ), 'not_found_in_trash' => __( 'Nothing found in Trash', 'ccb-core' ), 'parent_item_colon' => '', ), - 'description' => __( 'These are the events that are synchronized with your Church Community Builder software.', 'ccb-core' ), + 'description' => sprintf( __( 'These are the %s that are synchronized with your Church Community Builder software.', 'ccb-core' ), $plural ), 'public' => true, - 'publicly_queryable' => true, - 'exclude_from_search' => false, - 'show_ui' => true, - 'show_in_nav_menus' => true, + 'publicly_queryable' => $publicly_queryable, + 'exclude_from_search' => $exclude_from_search, + 'show_ui' => $show_ui, + 'show_in_nav_menus' => $show_in_nav_menus, 'query_var' => true, 'menu_position' => 8, 'menu_icon' => 'dashicons-calendar', - 'rewrite' => array( 'slug' => 'events' ), - 'has_archive' => 'events', + 'rewrite' => $rewrite, + 'has_archive' => $has_archive, 'capability_type' => 'post', 'hierarchical' => false, 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields', 'sticky' ), ); + + } + + /** + * Configure the options that users are allowed to set + * + * @since 1.0.0 + * @param array $settings The settings definitions. + * @return array + */ + public function get_post_settings_definitions( $settings ) { + + $settings['ccb_core_settings_calendar'] = array( + 'page_title' => esc_html__( 'Public Events', 'ccb-core' ), + 'sections' => array( + 'calendar' => array( + 'section_title' => esc_html__( 'Public Events', 'ccb-core' ), + 'fields' => array( + 'calendar_enabled' => array( + 'field_title' => esc_html__( 'Enable Events', 'ccb-core' ), + 'field_render_function' => 'render_switch', + 'field_validation' => 'switch', + ), + 'calendar_name' => array( + 'field_title' => esc_html__( 'Event Display Name (Plural)', 'ccb-core' ), + 'field_render_function' => 'render_text', + 'field_placeholder' => esc_html__( 'Events', 'ccb-core' ), + 'field_validation' => 'alphanumeric_extended', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1}' ), + 'field_tooltip' => esc_html__( 'This is what you call the events in your church (i.e. Meetups, Hangouts, etc.).', 'ccb-core' ), + ), + 'calendar_name_singular' => array( + 'field_title' => esc_html__( 'Event Display Name (Singular)', 'ccb-core' ), + 'field_render_function' => 'render_text', + 'field_placeholder' => esc_html__( 'Event', 'ccb-core' ), + 'field_validation' => 'alphanumeric_extended', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1}' ), + 'field_tooltip' => esc_html__( 'This is the singular name of what you call the events in your church (i.e. Meetup, Hangout, etc.).', 'ccb-core' ), + ), + 'calendar_slug' => array( + 'field_title' => esc_html__( 'Events URL Name', 'ccb-core' ), + 'field_render_function' => 'render_text', + 'field_placeholder' => 'events', + 'field_validation' => 'slug', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1}' ), + 'field_tooltip' => esc_html__( 'This is typically where your theme will display all the events. WordPress calls this a "slug".', 'ccb-core' ), + ), + 'calendar_advanced' => array( + 'field_title' => esc_html__( 'Enable Advanced Settings (Optional)', 'ccb-core' ), + 'field_render_function' => 'render_switch', + 'field_validation' => 'switch', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1}' ), + ), + 'calendar_date_range_type' => array( + 'field_title' => esc_html__( 'Date Range Type', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'relative' => esc_html__( 'Relative Range', 'ccb-core' ), + 'specific' => esc_html__( 'Specific Range', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'relative', + 'field_attributes' => array( 'class' => 'date-range-type', 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1}' ), + 'field_tooltip' => sprintf( + esc_html__( + 'Relative: For example, always get the events from "One week ago", up to "Eight weeks from now".%1$s + This is the best setting for most churches.%2$s + Specific: For example, only get events from "6/1/2018" to "12/1/2018".%3$s + This setting is best if you want to tightly manage the events that get published.', + 'ccb-core' ), + '
', + '

', + '
' + ), + ), + 'calendar_relative_weeks_past' => array( + 'field_title' => esc_html__( 'How Far Back?', 'ccb-core' ), + 'field_render_function' => 'render_slider', + 'field_options' => array( + 'min' => '0', + 'max' => '26', + 'units' => 'weeks', + ), + 'field_default' => 1, + 'field_validation' => '', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1,"calendar_date_range_type":"relative"}' ), + 'field_tooltip' => esc_html__( 'Every time we synchronize, how many weeks in the past should we look? (0 would be "today")', 'ccb-core' ), + ), + 'calendar_relative_weeks_future' => array( + 'field_title' => esc_html__( 'How Into The Future?', 'ccb-core' ), + 'field_render_function' => 'render_slider', + 'field_options' => array( + 'min' => '1', + 'max' => '52', + 'units' => 'weeks', + ), + 'field_default' => 16, + 'field_validation' => '', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1,"calendar_date_range_type":"relative"}' ), + 'field_tooltip' => esc_html__( 'Every time we synchronize, how many weeks in the future should we look?', 'ccb-core' ), + ), + 'calendar_specific_start' => array( + 'field_title' => esc_html__( 'Specific Start Date', 'ccb-core' ), + 'field_render_function' => 'render_date_picker', + 'field_validation' => '', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1,"calendar_date_range_type":"specific"}' ), + 'field_tooltip' => sprintf( + esc_html__( + 'When synchronizing, we should get events that start after this date.%s + (Leave empty to always start "today")', + 'ccb-core' ), + '
' + ), + ), + 'calendar_specific_end' => array( + 'field_title' => esc_html__( 'Specific End Date', 'ccb-core' ), + 'field_render_function' => 'render_date_picker', + 'field_validation' => '', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1,"calendar_date_range_type":"specific"}' ), + 'field_tooltip' => sprintf( + esc_html__( + 'When synchronizing, we should get events that start before this date.%s + (Setting this too far into the future may cause the API to timeout)', + 'ccb-core' ), + '
' + ), + ), + 'calendar_exclude_from_search' => array( + 'field_title' => esc_html__( 'Exclude From Search?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'no', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1}' ), + ), + 'calendar_publicly_queryable' => array( + 'field_title' => esc_html__( 'Publicly Queryable?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'yes', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1}' ), + ), + 'calendar_show_ui' => array( + 'field_title' => esc_html__( 'Show In Admin UI?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'yes', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1}' ), + ), + 'calendar_show_in_nav_menus' => array( + 'field_title' => esc_html__( 'Show In Navigation Menus?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'no', + 'field_attributes' => array( 'data-requires' => '{"calendar_enabled":1,"calendar_advanced":1}' ), + ), + ), + ), + ), + ); + + return $settings; + } + + /** + * Define the mapping of CCB API fields to the Post fields + * + * @since 1.0.0 + * @param array $map A collection of mappings from the API to WordPress. + * @return array + */ + public function get_post_type_map( $map ) { + if ( $this->enabled ) { + $options = CCB_Core_Helpers::instance()->get_options(); + $calendar_options = $this->get_calendar_options( $options ); + + $map[ $this->name ] = [ + 'service' => 'public_calendar_listing', + 'data' => [ + 'date_start' => $calendar_options['date_start'], + 'date_end' => $calendar_options['date_end'], + ], + 'nodes' => [ 'items', 'item' ], + 'fields' => [ + 'event_name' => 'post_title', + 'event_description' => 'post_content', + 'date' => 'post_meta', + 'start_time' => 'post_meta', + 'end_time' => 'post_meta', + 'event_duration' => 'post_meta', + 'location' => 'post_meta', + ], + ]; + } + return $map; + } + + private function get_calendar_options( $options ) { + // By default, set some sane limits. + $calendar_options = [ + 'date_start' => date( 'Y-m-d', strtotime( '1 weeks ago' ) ), + 'date_end' => date( 'Y-m-d', strtotime( '+16 weeks' ) ), + ]; + + // If the user has set a preferred date range type. + if ( ! empty( $options['calendar_date_range_type'] ) ) { + + if ( 'relative' === $options['calendar_date_range_type'] ) { + + $calendar_options['date_start'] = date( 'Y-m-d', strtotime( $options['calendar_relative_weeks_past'] . ' weeks ago' ) ); + $calendar_options['date_end'] = date( 'Y-m-d', strtotime( '+' . $options['calendar_relative_weeks_future'] . ' weeks' ) ); + + } elseif ( 'specific' === $options['calendar_date_range_type'] ) { + + // For each date range, do not let the user go further than + // 1 year in the past or 1 year into the future to prevent + // them from blowing up their server. + if ( ! empty( $options['calendar_specific_start'] ) ) { + $last_year = strtotime( '1 year ago' ); + $start_timestamp = strtotime( $options['calendar_specific_start'] ); + + if ( $last_year < $start_timestamp ) { + $calendar_options['date_start'] = date( 'Y-m-d', $start_timestamp ); + } else { + $calendar_options['date_start'] = date( 'Y-m-d', $last_year ); + } + } + + if ( ! empty( $options['calendar_specific_end'] ) ) { + $next_year = strtotime( '+1 year' ); + $end_timestamp = strtotime( $options['calendar_specific_end'] ); + + if ( $next_year > $end_timestamp ) { + $calendar_options['date_end'] = date( 'Y-m-d', $end_timestamp ); + } else { + $calendar_options['date_end'] = date( 'Y-m-d', $next_year ); + } + } + + } + } + + return $calendar_options; } } diff --git a/includes/post-types/class-ccb-core-cpt.php b/includes/post-types/class-ccb-core-cpt.php index f55fd73..227f879 100644 --- a/includes/post-types/class-ccb-core-cpt.php +++ b/includes/post-types/class-ccb-core-cpt.php @@ -26,78 +26,27 @@ abstract class CCB_Core_CPT { public $name; /** - * The specific custom post type options + * Whether or not this post type is enabled. (Set by child class). * - * @var array + * @var bool */ - protected $cpt_options = array(); + public $enabled; /** * Initialize the class */ public function __construct() { - $plugin_options = CCB_Core_Helpers::instance()->get_options(); + add_filter( 'ccb_core_post_type_settings_definitions', array( $this, 'get_post_settings_definitions' ) ); - // If this CPT is enabled, merge the defaults and set the registration hook. - if ( ! empty( $plugin_options[ $this->name ]['enabled'] ) ) { - $this->cpt_options = wp_parse_args( $this->get_user_cpt_options( $plugin_options ), $this->get_cpt_defaults() ); + // If this custom post type is enabled, merge the defaults and set the registration hook. + if ( $this->enabled ) { add_action( 'init', array( $this, 'register_post_type' ) ); + add_filter( 'ccb_core_post_type_map', array( $this, 'get_post_type_map' ) ); } } - /** - * Get the dynamic CPT options based on - * what the user may have set - * - * @param array $plugin_options The entire options array. - * @return array - */ - protected function get_user_cpt_options( $plugin_options ) { - - $user_cpt_options = array(); - - if ( ! empty( $plugin_options[ $this->name ]['cpt_options']['name'] ) ) { - $user_cpt_options['labels']['name'] = $plugin_options[ $this->name ]['cpt_options']['name']; - $user_cpt_options['labels']['all_items'] = sprintf( __( 'All %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['name'] ); - $user_cpt_options['labels']['search_items'] = sprintf( __( 'Search %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['name'] ); - } - - if ( ! empty( $plugin_options[ $this->name ]['cpt_options']['singular_name'] ) ) { - $user_cpt_options['labels']['singular_name'] = $plugin_options[ $this->name ]['cpt_options']['singular_name']; - $user_cpt_options['labels']['add_new_item'] = sprintf( __( 'Add New %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['singular_name'] ); - $user_cpt_options['labels']['edit_item'] = sprintf( __( 'Edit %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['singular_name'] ); - $user_cpt_options['labels']['new_item'] = sprintf( __( 'New %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['singular_name'] ); - $user_cpt_options['labels']['view_item'] = sprintf( __( 'View %s', 'ccb-core' ), $plugin_options[ $this->name ]['cpt_options']['singular_name'] ); - } - - if ( ! empty( $plugin_options[ $this->name ]['cpt_options']['slug'] ) ) { - $user_cpt_options['rewrite'] = array( 'slug' => $plugin_options[ $this->name ]['cpt_options']['slug'] ); - $user_cpt_options['has_archive'] = $plugin_options[ $this->name ]['cpt_options']['slug']; - } - - // The remaining options are boolean, so directly set their values if the option is set. - if ( isset( $plugin_options[ $this->name ]['cpt_options']['publicly_queryable'] ) ) { - $user_cpt_options['publicly_queryable'] = $plugin_options[ $this->name ]['cpt_options']['publicly_queryable']; - } - - if ( isset( $plugin_options[ $this->name ]['cpt_options']['exclude_from_search'] ) ) { - $user_cpt_options['exclude_from_search'] = $plugin_options[ $this->name ]['cpt_options']['exclude_from_search']; - } - - if ( isset( $plugin_options[ $this->name ]['cpt_options']['show_ui'] ) ) { - $user_cpt_options['show_ui'] = $plugin_options[ $this->name ]['cpt_options']['show_ui']; - } - - if ( isset( $plugin_options[ $this->name ]['cpt_options']['show_in_nav_menus'] ) ) { - $user_cpt_options['show_in_nav_menus'] = $plugin_options[ $this->name ]['cpt_options']['show_in_nav_menus']; - } - - return $user_cpt_options; - - } - /** * Register the custom post type * @@ -106,7 +55,7 @@ protected function get_user_cpt_options( $plugin_options ) { * @return void */ public function register_post_type() { - register_post_type( $this->name, $this->cpt_options ); + register_post_type( $this->name, $this->get_post_args() ); } /** @@ -118,12 +67,30 @@ public function get_post_type_object() { return get_post_type_object( $this->name ); } + /** + * Setup the custom post type $args + * + * @since 1.0.0 + * @return array $args for register_post_type + */ + abstract public function get_post_args(); + /** * Setup the default CPT options * * @since 1.0.0 - * @return array Default options for register_post_type + * @param array $settings The settings definitions. + * @return array The configuration for the options settable by the user + */ + abstract public function get_post_settings_definitions( $settings ); + + /** + * Define the mapping of CCB API fields to the Post fields + * + * @since 1.0.0 + * @param array $map A collection of mappings from the API to WordPress. + * @return array */ - abstract public function get_cpt_defaults(); + abstract public function get_post_type_map( $map ); } diff --git a/includes/post-types/class-ccb-core-cpts.php b/includes/post-types/class-ccb-core-cpts.php deleted file mode 100644 index fbee973..0000000 --- a/includes/post-types/class-ccb-core-cpts.php +++ /dev/null @@ -1,400 +0,0 @@ - - */ -class CCB_Core_CPT { - - /** - * The options we should use to register the groups CPT - * - * @since 0.9.0 - * @access protected - * @var array $groups_cpt_options - */ - protected $groups_cpt_options = array(); - - /** - * The options we should use to register the calendar CPT - * - * @since 0.9.0 - * @access protected - * @var array $calendar_cpt_options - */ - protected $calendar_cpt_options = array(); - - /** - * Initialize the class and set its properties. - * - * @since 0.9.0 - */ - public function __construct() { - - parent::__construct(); - - } - - /** - * Determine which CCB custom post types should be registered - * - * @access public - * @since 0.9.0 - * @return void - */ - public function initialize() { - - $settings = get_option( $this->plugin_settings_name ); - - if ( isset( $settings['groups-enabled'] ) && $settings['groups-enabled'] == 1 ) { - - $this->groups_cpt_options['name'] = ( empty( $settings['groups-name'] ) ? 'Groups' : $settings['groups-name'] ); - $this->groups_cpt_options['slug'] = ( empty( $settings['groups-slug'] ) ? 'groups' : $settings['groups-slug'] ); - $this->groups_cpt_options['singular_name'] = rtrim( $this->groups_cpt_options['name'], 's' ); // this is ghetto - $this->groups_cpt_options['exclude_from_search'] = ( $settings['groups-exclude-from-search'] == 'yes' ? true : false ); - $this->groups_cpt_options['publicly_queryable'] = ( $settings['groups-publicly-queryable'] == 'yes' ? true : false ); - $this->groups_cpt_options['show_ui'] = ( $settings['groups-show-ui'] == 'yes' ? true : false ); - $this->groups_cpt_options['show_in_nav_menus'] = ( $settings['groups-show-in-nav-menus'] == 'yes' ? true : false ); - - $this->register_groups(); - - } - - if ( isset( $settings['calendar-enabled'] ) && $settings['calendar-enabled'] == 1 ) { - - $this->calendar_cpt_options['name'] = ( empty( $settings['calendar-name'] ) ? 'Events' : $settings['calendar-name'] ); - $this->calendar_cpt_options['slug'] = ( empty( $settings['calendar-slug'] ) ? 'events' : $settings['calendar-slug'] ); - $this->calendar_cpt_options['singular_name'] = rtrim( $this->calendar_cpt_options['name'], 's' ); // this is ghetto - $this->calendar_cpt_options['exclude_from_search'] = ( $settings['calendar-exclude-from-search'] == 'yes' ? true : false ); - $this->calendar_cpt_options['publicly_queryable'] = ( $settings['calendar-publicly-queryable'] == 'yes' ? true : false ); - $this->calendar_cpt_options['show_ui'] = ( $settings['calendar-show-ui'] == 'yes' ? true : false ); - $this->calendar_cpt_options['show_in_nav_menus'] = ( $settings['calendar-show-in-nav-menus'] == 'yes' ? true : false ); - - $this->register_calendar(); - - } - } - - /** - * Setup the CCB Groups custom post type and its taxonomies - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function register_groups() { - - register_post_type( $this->plugin_name . '-groups', - array( 'labels' => - array( - 'name' => $this->groups_cpt_options['name'], - 'singular_name' => $this->groups_cpt_options['singular_name'], - 'all_items' => __( 'All ' . $this->groups_cpt_options['name'], $this->plugin_name ), - 'add_new' => __( 'Add New', $this->plugin_name ), - 'add_new_item' => __( 'Add New ' . $this->groups_cpt_options['singular_name'], $this->plugin_name ), - 'edit' => __( 'Edit', $this->plugin_name ), - 'edit_item' => __( 'Edit ' . $this->groups_cpt_options['name'], $this->plugin_name ), - 'new_item' => __( 'New ' . $this->groups_cpt_options['singular_name'], $this->plugin_name ), - 'view_item' => __( 'View ' . $this->groups_cpt_options['singular_name'], $this->plugin_name ), - 'search_items' => __( 'Search ' . $this->groups_cpt_options['singular_name'], $this->plugin_name ), - 'not_found' => __( 'Nothing found in the Database.', $this->plugin_name ), - 'not_found_in_trash' => __( 'Nothing found in Trash', $this->plugin_name ), - 'parent_item_colon' => '' - ), - 'description' => __( 'These are the groups that are synchronized with your Church Community Builder software.', $this->plugin_name ), - 'public' => true, - 'publicly_queryable' => $this->groups_cpt_options['publicly_queryable'], - 'exclude_from_search' => $this->groups_cpt_options['exclude_from_search'], - 'show_ui' => $this->groups_cpt_options['show_ui'], - 'show_in_nav_menus' => $this->groups_cpt_options['show_in_nav_menus'], - 'query_var' => true, - 'menu_position' => 8, - 'menu_icon' => 'dashicons-groups', - 'rewrite' => array( 'slug' => $this->groups_cpt_options['slug'] ), - 'has_archive' => $this->groups_cpt_options['slug'], - 'capability_type' => 'post', - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields', 'sticky' ), - ) - ); - - $groups_taxonomies = self::get_groups_taxonomy_map(); - foreach ( $groups_taxonomies as $taxonomy_name=>$taxonomy ) { - $taxonomy_options = array( - 'hierarchical' => $taxonomy['hierarchical'], - 'labels' => array( - 'name' => $taxonomy['name_plural'], - 'singular_name' => $taxonomy['name'], - 'search_items' => "Search {$taxonomy['name_plural']}", - 'all_items' => "All {$taxonomy['name_plural']}", - 'parent_item' => "Parent {$taxonomy['name']}", - 'parent_item_colon' => "Parent {$taxonomy['name']}:", - 'edit_item' => "Edit {$taxonomy['name']}", - 'update_item' => "Update {$taxonomy['name']}", - 'add_new_item' => "Add New {$taxonomy['name']}", - 'new_item_name' => "New {$taxonomy['name']}" - ), - 'show_admin_column' => true, - 'show_ui' => true, - 'query_var' => true, - ); - - register_taxonomy( $taxonomy_name, "{$this->plugin_name}-groups", $taxonomy_options ); - } - - } - - /** - * Setup the CCB Events custom post type and its taxonomies - * - * @access protected - * @since 0.9.0 - * @return void - */ - protected function register_calendar() { - - register_post_type( $this->plugin_name . '-calendar', - array( 'labels' => - array( - 'name' => $this->calendar_cpt_options['name'], - 'singular_name' => $this->calendar_cpt_options['singular_name'], - 'all_items' => __( 'All ' . $this->calendar_cpt_options['name'], $this->plugin_name ), - 'add_new' => __( 'Add New', $this->plugin_name ), - 'add_new_item' => __( 'Add New ' . $this->calendar_cpt_options['singular_name'], $this->plugin_name ), - 'edit' => __( 'Edit', $this->plugin_name ), - 'edit_item' => __( 'Edit ' . $this->calendar_cpt_options['name'], $this->plugin_name ), - 'new_item' => __( 'New ' . $this->calendar_cpt_options['singular_name'], $this->plugin_name ), - 'view_item' => __( 'View ' . $this->calendar_cpt_options['singular_name'], $this->plugin_name ), - 'search_items' => __( 'Search ' . $this->calendar_cpt_options['singular_name'], $this->plugin_name ), - 'not_found' => __( 'Nothing found in the Database.', $this->plugin_name ), - 'not_found_in_trash' => __( 'Nothing found in Trash', $this->plugin_name ), - 'parent_item_colon' => '' - ), - 'description' => __( 'These are the calendar that are synchronized with your Church Community Builder software.', $this->plugin_name ), - 'public' => true, - 'publicly_queryable' => $this->calendar_cpt_options['publicly_queryable'], - 'exclude_from_search' => $this->calendar_cpt_options['exclude_from_search'], - 'show_ui' => $this->calendar_cpt_options['show_ui'], - 'show_in_nav_menus' => $this->calendar_cpt_options['show_in_nav_menus'], - 'query_var' => true, - 'menu_position' => 8, - 'menu_icon' => 'dashicons-calendar', - 'rewrite' => array( 'slug' => $this->calendar_cpt_options['slug'] ), - 'has_archive' => $this->calendar_cpt_options['slug'], - 'capability_type' => 'post', - 'hierarchical' => false, - 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields', 'sticky' ), - ) - ); - - $calendar_taxonomies = self::get_calendar_taxonomy_map(); - foreach ( $calendar_taxonomies as $taxonomy_name=>$taxonomy ) { - $taxonomy_options = array( - 'hierarchical' => $taxonomy['hierarchical'], - 'labels' => array( - 'name' => $taxonomy['name_plural'], - 'singular_name' => $taxonomy['name'], - 'search_items' => "Search {$taxonomy['name_plural']}", - 'all_items' => "All {$taxonomy['name_plural']}", - 'parent_item' => "Parent {$taxonomy['name']}", - 'parent_item_colon' => "Parent {$taxonomy['name']}:", - 'edit_item' => "Edit {$taxonomy['name']}", - 'update_item' => "Update {$taxonomy['name']}", - 'add_new_item' => "Add New {$taxonomy['name']}", - 'new_item_name' => "New {$taxonomy['name']}" - ), - 'show_admin_column' => true, - 'show_ui' => true, - 'query_var' => true, - ); - - register_taxonomy( $taxonomy_name, "{$this->plugin_name}-calendar", $taxonomy_options ); - } - - } - - /** - * Helper method to hold a map of structure from the groups custom post - * type custom fields to the API schema - * - * @static - * @access public - * @return array - */ - public static function get_groups_custom_fields_map() { - return array( - 'group_main_leader' => array( - 'api_mapping' => 'main_leader', - 'data_type' => 'object', - 'child_object' => array( - 'leader_full_name' => array( - 'api_mapping' => 'full_name', - 'data_type' => 'string' - ), - 'leader_email' => array( - 'api_mapping' => 'email', - 'data_type' => 'string' - ) - ) - ), - 'group_calendar_feed' => array( - 'api_mapping' => 'calendar_feed', - 'data_type' => 'string', - ), - 'addresses' => array( - 'api_mapping' => 'addresses', - 'data_type' => 'object', - 'child_object' => array( - 'address' => array( - 'api_mapping' => 'address', - 'data_type' => 'object', - 'child_object' => array( - 'longitude' => array( - 'api_mapping' => 'longitude', - 'data_type' => 'string' - ), - 'latitude' => array( - 'api_mapping' => 'latitude', - 'data_type' => 'string' - ), - 'address_line_1' => array( - 'api_mapping' => 'line_1', - 'data_type' => 'string' - ), - 'address_line_2' => array( - 'api_mapping' => 'line_2', - 'data_type' => 'string' - ), - ) - ), - ), - ), - ); - } - - /** - * Helper method to hold a map of structure from the calendar custom post - * type custom fields to the API schema - * - * @static - * @access public - * @return array - */ - public static function get_calendar_custom_fields_map() { - return array( - 'calendar_date' => array( - 'api_mapping' => 'date', - 'data_type' => 'string', - ), - 'calendar_start_time' => array( - 'api_mapping' => 'start_time', - 'data_type' => 'string', - ), - 'calendar_end_time' => array( - 'api_mapping' => 'end_time', - 'data_type' => 'string', - ), - 'calendar_duration' => array( - 'api_mapping' => 'event_duration', - 'data_type' => 'int', - ), - ); - } - - /** - * Helper method to hold a map of structure from the groups custom post - * type taxonomies to the API schema - * - * @static - * @access public - * @return array - */ - public static function get_groups_taxonomy_map() { - - return array( - 'group_areas' => array( - 'name' => 'Area', - 'name_plural' => 'Areas', - 'hierarchical' => true, - 'api_mapping' => 'area' - ), - 'group_days' => array( - 'name' => 'Day', - 'name_plural' => 'Days', - 'hierarchical' => true, - 'api_mapping' => 'meeting_day' - ), - 'group_types' => array( - 'name' => 'Type', - 'name_plural' => 'Types', - 'hierarchical' => true, - 'api_mapping' => 'group_type' - ), - 'group_times' => array( - 'name' => 'Time', - 'name_plural' => 'Times', - 'hierarchical' => true, - 'api_mapping' => 'meeting_time' - ), - 'group_departments' => array( - 'name' => 'Department', - 'name_plural' => 'Departments', - 'hierarchical' => true, - 'api_mapping' => 'department' - ), - 'group_tags' => array( - 'name' => 'Group Tag', - 'name_plural' => 'Group Tags', - 'hierarchical' => false, - 'api_mapping' => array( - 'childcare_provided' => 'Childcare Provided' - ) - ), - ); - } - - /** - * Helper method to hold a map of structure from the events custom post - * type taxonomies to the API schema - * - * @static - * @access public - * @return array - */ - public static function get_calendar_taxonomy_map() { - - return array( - 'calendar_event_type' => array( - 'name' => 'Type', - 'name_plural' => 'Types', - 'hierarchical' => true, - 'api_mapping' => 'event_type' - ), - 'calendar_group_name' => array( - 'name' => 'Group Name', - 'name_plural' => 'Group Names', - 'hierarchical' => true, - 'api_mapping' => 'group_name' - ), - 'calendar_grouping_name' => array( - 'name' => 'Grouping Name', - 'name_plural' => 'Grouping Names', - 'hierarchical' => true, - 'api_mapping' => 'grouping_name' - ), - ); - } - -} diff --git a/includes/post-types/class-ccb-core-group.php b/includes/post-types/class-ccb-core-group.php index 964b8be..2e5723b 100644 --- a/includes/post-types/class-ccb-core-group.php +++ b/includes/post-types/class-ccb-core-group.php @@ -23,48 +23,245 @@ class CCB_Core_Group extends CCB_Core_CPT { * * @var string */ - public $name = 'ccb-core-groups'; + public $name = 'ccb_core_groups'; /** - * Setup the default CPT options + * Initialize the class + */ + public function __construct() { + add_filter( 'ccb_core_entity_insert_allowed', [ $this, 'entity_insert_update_allowed' ], 10, 4 ); + add_filter( 'ccb_core_entity_update_allowed', [ $this, 'entity_insert_update_allowed' ], 10, 4 ); + add_filter( 'ccb_core_after_insert_update_post', [ $this, 'attach_group_image' ], 10, 5 ); + + $options = CCB_Core_Helpers::instance()->get_options(); + $this->enabled = ! empty( $options['groups_enabled'] ) ? true : false; + parent::__construct(); + } + + /** + * Setup the custom post type args * * @since 1.0.0 - * @return array Default options for register_post_type + * @return array $args for register_post_type */ - public function get_cpt_defaults() { + public function get_post_args() { + + $options = CCB_Core_Helpers::instance()->get_options(); + $plural = ! empty( $options['groups_name'] ) ? $options['groups_name'] : __( 'Groups', 'ccb-core' ); + $singular = ! empty( $options['groups_name_singular'] ) ? $options['groups_name_singular'] : __( 'Group', 'ccb-core' ); + $rewrite = ! empty( $options['groups_slug'] ) ? [ 'slug' => sanitize_title( $options['groups_slug'] ) ] : [ 'slug' => 'groups' ]; + $has_archive = ! empty( $options['groups_slug'] ) ? sanitize_title( $options['groups_slug'] ) : 'groups'; + $exclude_from_search = ! empty( $options['groups_exclude_from_search'] ) && 'yes' === $options['groups_exclude_from_search'] ? true : false; + $publicly_queryable = ! empty( $options['groups_publicly_queryable'] ) && 'no' === $options['groups_publicly_queryable'] ? false : true; + $show_ui = ! empty( $options['groups_show_ui'] ) && 'no' === $options['groups_show_ui'] ? false : true; + $show_in_nav_menus = ! empty( $options['groups_show_in_nav_menus'] ) && 'yes' === $options['groups_show_in_nav_menus'] ? true : false; + return array( 'labels' => array( - 'name' => __( 'Groups', 'ccb-core' ), - 'singular_name' => __( 'Group', 'ccb-core' ), - 'all_items' => __( 'All Groups', 'ccb-core' ), + 'name' => $plural, + 'singular_name' => $singular, + 'all_items' => sprintf( __( 'All %s', 'ccb-core' ), $plural ), 'add_new' => __( 'Add New', 'ccb-core' ), - 'add_new_item' => __( 'Add New Group', 'ccb-core' ), + 'add_new_item' => sprintf( __( 'Add New %s', 'ccb-core' ), $singular ), 'edit' => __( 'Edit', 'ccb-core' ), - 'edit_item' => __( 'Edit Group', 'ccb-core' ), - 'new_item' => __( 'New Group', 'ccb-core' ), - 'view_item' => __( 'View Group', 'ccb-core' ), - 'search_items' => __( 'Search Groups', 'ccb-core' ), + 'edit_item' => sprintf( __( 'Edit %s', 'ccb-core' ), $singular ), + 'new_item' => sprintf( __( 'New %s', 'ccb-core' ), $singular ), + 'view_item' => sprintf( __( 'View %s', 'ccb-core' ), $singular ), + 'search_items' => sprintf( __( 'Search %s', 'ccb-core' ), $plural ), 'not_found' => __( 'Nothing found in the Database.', 'ccb-core' ), 'not_found_in_trash' => __( 'Nothing found in Trash', 'ccb-core' ), 'parent_item_colon' => '', ), - 'description' => __( 'These are the groups that are synchronized with your Church Community Builder software.', 'ccb-core' ), + 'description' => sprintf( __( 'These are the %s that are synchronized with your Church Community Builder software.', 'ccb-core' ), $plural ), 'public' => true, - 'publicly_queryable' => true, - 'exclude_from_search' => false, - 'show_ui' => true, - 'show_in_nav_menus' => true, + 'publicly_queryable' => $publicly_queryable, + 'exclude_from_search' => $exclude_from_search, + 'show_ui' => $show_ui, + 'show_in_nav_menus' => $show_in_nav_menus, 'query_var' => true, 'menu_position' => 8, 'menu_icon' => 'dashicons-groups', - 'rewrite' => array( 'slug' => 'groups' ), - 'has_archive' => 'groups', + 'rewrite' => $rewrite, + 'has_archive' => $has_archive, 'capability_type' => 'post', 'hierarchical' => false, 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields', 'sticky' ), ); + } + /** + * Configure the options that users are allowed to set + * + * @since 1.0.0 + * @param array $settings The settings definitions. + * @return array + */ + public function get_post_settings_definitions( $settings ) { + + $settings['ccb_core_settings_groups'] = array( + 'page_title' => esc_html__( 'Groups', 'ccb-core' ), + 'sections' => array( + 'groups' => array( + 'section_title' => esc_html__( 'Groups', 'ccb-core' ), + 'fields' => array( + 'groups_enabled' => array( + 'field_title' => esc_html__( 'Enable Groups', 'ccb-core' ), + 'field_render_function' => 'render_switch', + 'field_validation' => 'switch', + ), + 'groups_name' => array( + 'field_title' => esc_html__( 'Groups Display Name (Plural)', 'ccb-core' ), + 'field_render_function' => 'render_text', + 'field_placeholder' => esc_html__( 'Groups', 'ccb-core' ), + 'field_validation' => 'alphanumeric_extended', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1}' ), + 'field_tooltip' => esc_html__( 'This is what you call the groups in your church (i.e. Home Groups, Connections, Life Groups, etc.).', 'ccb-core' ), + ), + 'groups_name_singular' => array( + 'field_title' => esc_html__( 'Groups Display Name (Singular)', 'ccb-core' ), + 'field_render_function' => 'render_text', + 'field_placeholder' => esc_html__( 'Group', 'ccb-core' ), + 'field_validation' => 'alphanumeric_extended', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1}' ), + 'field_tooltip' => esc_html__( 'This is the singular name of what you call the groups in your church (i.e. Home Group, Connection, Life Group, etc.).', 'ccb-core' ), + ), + 'groups_slug' => array( + 'field_title' => esc_html__( 'Groups URL Name', 'ccb-core' ), + 'field_render_function' => 'render_text', + 'field_placeholder' => 'groups', + 'field_validation' => 'slug', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1}' ), + 'field_tooltip' => esc_html__( 'This is typically where your theme will display all the groups. WordPress calls this a "slug".', 'ccb-core' ), + ), + 'groups_import_images' => array( + 'field_title' => esc_html__( 'Also Import Group Images?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'no', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1}' ), + 'field_tooltip' => sprintf( + esc_html__( + 'This will download the CCB Group Image and attach it as a Featured Image.%s + If you don\'t need group images, then disabling this feature will speed up the synchronization.', + 'ccb-core' ), + '
' + ), + ), + 'groups_advanced' => array( + 'field_title' => esc_html__( 'Enable Advanced Settings (Optional)', 'ccb-core' ), + 'field_render_function' => 'render_switch', + 'field_validation' => 'switch', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1}' ), + ), + 'groups_exclude_from_search' => array( + 'field_title' => esc_html__( 'Exclude From Search?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'no', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1,"groups_advanced":1}' ), + ), + 'groups_publicly_queryable' => array( + 'field_title' => esc_html__( 'Publicly Queryable?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'yes', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1,"groups_advanced":1}' ), + ), + 'groups_show_ui' => array( + 'field_title' => esc_html__( 'Show In Admin UI?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'yes', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1,"groups_advanced":1}' ), + ), + 'groups_show_in_nav_menus' => array( + 'field_title' => esc_html__( 'Show In Navigation Menus?', 'ccb-core' ), + 'field_render_function' => 'render_radio', + 'field_options' => array( + 'yes' => esc_html__( 'Yes', 'ccb-core' ), + 'no' => esc_html__( 'No', 'ccb-core' ), + ), + 'field_validation' => '', + 'field_default' => 'no', + 'field_attributes' => array( 'data-requires' => '{"groups_enabled":1,"groups_advanced":1}' ), + ), + ), + ), + ), + ); + + return $settings; + + } + + /** + * Define the mapping of CCB API fields to the Post fields + * + * @since 1.0.0 + * @param array $map A collection of mappings from the API to WordPress. + * @return array + */ + public function get_post_type_map( $map ) { + if ( $this->enabled ) { + $options = CCB_Core_Helpers::instance()->get_options(); + $include_image_link = ! empty( $options['groups_import_images'] ) && 'yes' === $options['groups_import_images'] ? true : false; + + $map[ $this->name ] = [ + 'service' => 'group_profiles', + 'data' => [ + 'include_participants' => false, + 'include_image_link' => $include_image_link, + ], + 'nodes' => [ 'groups', 'group' ], + 'fields' => [ + 'name' => 'post_title', + 'description' => 'post_content', + 'main_leader' => 'post_meta', + 'calendar_feed' => 'post_meta', + 'current_members' => 'post_meta', + 'group_capacity' => 'post_meta', + 'addresses' => 'post_meta', + ], + ]; + } + return $map; + } + + public function entity_insert_update_allowed( $allowed, $entity_id, $entity, $post_type ) { + if ( $this->name === $post_type ) { + // Only allow active, publicly listed groups to be imported. + if ( 'true' === (string) $entity->inactive || 'false' === (string) $entity->public_search_listed ) { + $allowed = false; + } + } + return $allowed; + } + + public function attach_group_image( $entity, $settings, $args, $post_type, $post_id ) { + if ( $this->name === $post_type && $settings['data']['include_image_link'] ) { + $image_url = (string) $entity->image; + if ( $image_url ) { + CCB_Core_Helpers::instance()->download_image( $image_url, $args['post_title'], $post_id ); + } + } + } } new CCB_Core_Group(); diff --git a/includes/taxonomies/class-ccb-core-calendar-event-type.php b/includes/taxonomies/class-ccb-core-calendar-event-type.php index 7ba1615..62a71a2 100644 --- a/includes/taxonomies/class-ccb-core-calendar-event-type.php +++ b/includes/taxonomies/class-ccb-core-calendar-event-type.php @@ -30,7 +30,7 @@ class CCB_Core_Calendar_Event_Type extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-calendar' ); + public $object_types = array( 'ccb_core_calendar' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Calendar_Event_Type extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Types', 'ccb-core' ), diff --git a/includes/taxonomies/class-ccb-core-calendar-group-name.php b/includes/taxonomies/class-ccb-core-calendar-group-name.php index cfaa411..9e6e02e 100644 --- a/includes/taxonomies/class-ccb-core-calendar-group-name.php +++ b/includes/taxonomies/class-ccb-core-calendar-group-name.php @@ -30,7 +30,7 @@ class CCB_Core_Calendar_Group_Name extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-calendar' ); + public $object_types = array( 'ccb_core_calendar' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Calendar_Group_Name extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Group Names', 'ccb-core' ), diff --git a/includes/taxonomies/class-ccb-core-calendar-grouping-name.php b/includes/taxonomies/class-ccb-core-calendar-grouping-name.php index 01fe316..7547fb3 100644 --- a/includes/taxonomies/class-ccb-core-calendar-grouping-name.php +++ b/includes/taxonomies/class-ccb-core-calendar-grouping-name.php @@ -30,7 +30,7 @@ class CCB_Core_Calendar_Grouping_Name extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-calendar' ); + public $object_types = array( 'ccb_core_calendar' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Calendar_Grouping_Name extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Grouping Names', 'ccb-core' ), diff --git a/includes/taxonomies/class-ccb-core-group-area.php b/includes/taxonomies/class-ccb-core-group-area.php index f330957..87c57f9 100644 --- a/includes/taxonomies/class-ccb-core-group-area.php +++ b/includes/taxonomies/class-ccb-core-group-area.php @@ -30,7 +30,7 @@ class CCB_Core_Group_Area extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-groups' ); + public $object_types = array( 'ccb_core_groups' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Group_Area extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Areas', 'ccb-core' ), diff --git a/includes/taxonomies/class-ccb-core-group-day.php b/includes/taxonomies/class-ccb-core-group-day.php index 819474f..f17891f 100644 --- a/includes/taxonomies/class-ccb-core-group-day.php +++ b/includes/taxonomies/class-ccb-core-group-day.php @@ -30,7 +30,7 @@ class CCB_Core_Group_Day extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-groups' ); + public $object_types = array( 'ccb_core_groups' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Group_Day extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Days', 'ccb-core' ), diff --git a/includes/taxonomies/class-ccb-core-group-department.php b/includes/taxonomies/class-ccb-core-group-department.php index e051967..d46f386 100644 --- a/includes/taxonomies/class-ccb-core-group-department.php +++ b/includes/taxonomies/class-ccb-core-group-department.php @@ -30,7 +30,7 @@ class CCB_Core_Group_Department extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-groups' ); + public $object_types = array( 'ccb_core_groups' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Group_Department extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Departments', 'ccb-core' ), diff --git a/includes/taxonomies/class-ccb-core-group-tag.php b/includes/taxonomies/class-ccb-core-group-tag.php index 7734efb..7d5aaa2 100644 --- a/includes/taxonomies/class-ccb-core-group-tag.php +++ b/includes/taxonomies/class-ccb-core-group-tag.php @@ -30,7 +30,7 @@ class CCB_Core_Group_Tag extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-groups' ); + public $object_types = array( 'ccb_core_groups' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Group_Tag extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Group Tags', 'ccb-core' ), @@ -56,7 +56,9 @@ public static function get_taxonomy_mapping() { 'show_admin_column' => true, 'show_ui' => true, 'query_var' => true, - 'api_mapping' => array( 'childcare_provided' => __( 'Childcare Provided' ) ), // The field key from the CCB API. + 'api_mapping' => array( + 'childcare_provided' => __( 'Childcare Provided', 'ccb-core' ), // The field key from the CCB API. + ), ); } diff --git a/includes/taxonomies/class-ccb-core-group-time.php b/includes/taxonomies/class-ccb-core-group-time.php index 29bc51c..916f281 100644 --- a/includes/taxonomies/class-ccb-core-group-time.php +++ b/includes/taxonomies/class-ccb-core-group-time.php @@ -30,7 +30,7 @@ class CCB_Core_Group_Time extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-groups' ); + public $object_types = array( 'ccb_core_groups' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Group_Time extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Times', 'ccb-core' ), diff --git a/includes/taxonomies/class-ccb-core-group-type.php b/includes/taxonomies/class-ccb-core-group-type.php index 156036d..ab104a1 100644 --- a/includes/taxonomies/class-ccb-core-group-type.php +++ b/includes/taxonomies/class-ccb-core-group-type.php @@ -30,7 +30,7 @@ class CCB_Core_Group_Type extends CCB_Core_Taxonomy { * * @var array */ - public $object_types = array( 'ccb-core-groups' ); + public $object_types = array( 'ccb_core_groups' ); /** * Setup the default taxonomy mappings @@ -38,7 +38,7 @@ class CCB_Core_Group_Type extends CCB_Core_Taxonomy { * @since 1.0.0 * @return array Default options for register_taxonomy */ - public static function get_taxonomy_mapping() { + public static function get_taxonomy_args() { return array( 'labels' => array( 'name' => __( 'Types', 'ccb-core' ), diff --git a/includes/taxonomies/class-ccb-core-taxonomy.php b/includes/taxonomies/class-ccb-core-taxonomy.php index aff5458..58ffeaa 100644 --- a/includes/taxonomies/class-ccb-core-taxonomy.php +++ b/includes/taxonomies/class-ccb-core-taxonomy.php @@ -37,6 +37,7 @@ abstract class CCB_Core_Taxonomy { */ public function __construct() { add_action( 'init', array( $this, 'register_taxonomy' ) ); + add_filter( 'ccb_core_taxonomy_map', [ $this, 'get_taxonomy_map' ] ); } /** @@ -45,12 +46,30 @@ public function __construct() { * @return void */ public function register_taxonomy() { - register_taxonomy( $this->name, $this->object_types, static::get_taxonomy_mapping() ); + register_taxonomy( $this->name, $this->object_types, static::get_taxonomy_args() ); + } + + /** + * Define the mapping of CCB API fields to this taxonomy + * + * @since 1.0.0 + * @param array $map A collection of mappings from the API to WordPress. + * @return array + */ + public function get_taxonomy_map( $map ) { + if ( ! empty( $this->object_types ) ) { + foreach ( $this->object_types as $object_type ) { + $taxonomy_args = static::get_taxonomy_args(); + $hierarchical = ! empty( $taxonomy_args['hierarchical'] ) ? 'hierarchical' : 'nonhierarchical'; + $map[ $object_type ]['taxonomies'][ $hierarchical ][ $this->name ] = $taxonomy_args['api_mapping']; + } + } + return $map; } /** * Register the taxonomy. */ - abstract public static function get_taxonomy_mapping(); + abstract public static function get_taxonomy_args(); } diff --git a/js/ccb-core-admin.js b/js/ccb-core-admin.js index a83ee38..8e62f9b 100644 --- a/js/ccb-core-admin.js +++ b/js/ccb-core-admin.js @@ -89,7 +89,7 @@ var data = { 'action': 'get_latest_sync', - 'nextNonce': CCB_CORE_SETTINGS.nextNonce + 'nonce': CCB_CORE_SETTINGS.nonce }; $.post(ajaxurl, data, function(response) { @@ -108,7 +108,7 @@ var data = { 'action': 'poll_sync', - 'nextNonce': CCB_CORE_SETTINGS.nextNonce + 'nonce': CCB_CORE_SETTINGS.nonce }; $.post(ajaxurl, data, function(response) { @@ -145,7 +145,7 @@ var data = { 'action': 'test_credentials', - 'nextNonce': CCB_CORE_SETTINGS.nextNonce + 'nonce': CCB_CORE_SETTINGS.nonce }; $.post(ajaxurl, data, function(response) { @@ -154,7 +154,7 @@ $spinner.removeClass('is-active'); if (response.success === false) { - $testLoginWrapper.append('
' + response.message + '
'); + $testLoginWrapper.append('
' + response.data + '
'); } else if (typeof response.services !== 'undefined' && response.services.length > 0) { @@ -187,7 +187,7 @@ var data = { 'action': 'sync', - 'nextNonce': CCB_CORE_SETTINGS.nextNonce + 'nonce': CCB_CORE_SETTINGS.nonce }; $.post(ajaxurl, data, function(response) { diff --git a/lib/class-ccb-core-vendor-encryption.php b/lib/class-ccb-core-vendor-encryption.php index eb82917..f4d36bb 100644 --- a/lib/class-ccb-core-vendor-encryption.php +++ b/lib/class-ccb-core-vendor-encryption.php @@ -114,16 +114,20 @@ public function encrypt( $data, $key ) { * @returns array An array of keys ( a cipher key, a mac key, and a IV ) */ protected function get_keys( $salt, $key ) { - $iv_size = mcrypt_get_iv_size( $this->cipher, $this->mode ); - $key_size = mcrypt_get_key_size( $this->cipher, $this->mode ); - $length = 2 * $key_size + $iv_size; - - $key = $this->pbkdf2( 'sha512', $key, $salt, $this->rounds, $length ); - - $cipher_key = substr( $key, 0, $key_size ); - $mac_key = substr( $key, $key_size, $key_size ); - $iv = substr( $key, 2 * $key_size ); - return array( $cipher_key, $mac_key, $iv ); + if ( function_exists( 'mcrypt_get_iv_size' ) ) { + $iv_size = mcrypt_get_iv_size( $this->cipher, $this->mode ); + $key_size = mcrypt_get_key_size( $this->cipher, $this->mode ); + $length = 2 * $key_size + $iv_size; + + $key = $this->pbkdf2( 'sha512', $key, $salt, $this->rounds, $length ); + + $cipher_key = substr( $key, 0, $key_size ); + $mac_key = substr( $key, $key_size, $key_size ); + $iv = substr( $key, 2 * $key_size ); + return array( $cipher_key, $mac_key, $iv ); + } else { + return false; + } } /** From 52afd02204ca408b91a877dd02adbfd852396915 Mon Sep 17 00:00:00 2001 From: Jared Cobb Date: Mon, 1 Jan 2018 11:18:07 -0700 Subject: [PATCH 03/16] Final commit for main refactor --- README.txt | 2 +- ccb-core.php | 7 +- css/ccb-core-admin.css | 21 +- includes/class-ccb-core-activator.php | 13 + includes/class-ccb-core-admin-ajax.php | 158 ++- includes/class-ccb-core-api-old.php | 961 ------------------ includes/class-ccb-core-api.php | 54 +- includes/class-ccb-core-cron.php | 113 ++ includes/class-ccb-core-helpers.php | 41 +- includes/class-ccb-core-plugin.php | 146 --- includes/class-ccb-core-settings-field.php | 28 +- includes/class-ccb-core-settings-page.php | 4 +- includes/class-ccb-core-settings-section.php | 12 +- includes/class-ccb-core-settings.php | 15 +- includes/class-ccb-core-synchronizer.php | 609 +++++++++-- includes/class-ccb-core.php | 8 + .../post-types/class-ccb-core-calendar.php | 26 +- includes/post-types/class-ccb-core-cpt.php | 14 +- includes/post-types/class-ccb-core-group.php | 37 +- .../taxonomies/class-ccb-core-group-day.php | 2 + .../taxonomies/class-ccb-core-taxonomy.php | 2 +- js/ccb-core-admin.js | 139 +-- 22 files changed, 986 insertions(+), 1426 deletions(-) delete mode 100644 includes/class-ccb-core-api-old.php create mode 100644 includes/class-ccb-core-cron.php delete mode 100644 includes/class-ccb-core-plugin.php diff --git a/README.txt b/README.txt index 0af1275..bf04ee2 100644 --- a/README.txt +++ b/README.txt @@ -2,7 +2,7 @@ Contributors: jaredcobb Tags: ccb, church, api, chms Requires at least: 4.4.0 -Tested up to: 4.7.2 +Tested up to: 4.9.1 Stable tag: 1.0.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html diff --git a/ccb-core.php b/ccb-core.php index 9cc25c6..5ed250e 100644 --- a/ccb-core.php +++ b/ccb-core.php @@ -19,7 +19,7 @@ * Domain Path: /languages */ -// do not allow direct access to this file. +// Do not allow direct access to this file. if ( ! defined( 'WPINC' ) ) { die; } @@ -29,11 +29,12 @@ define( 'CCB_CORE_BASENAME', plugin_basename( __FILE__ ) ); define( 'CCB_CORE_VERSION', '1.0.0' ); -// code that runs during plugin activation. +// Code that runs during plugin activation and deactivation. require_once CCB_CORE_PATH . 'includes/class-ccb-core-activator.php'; register_activation_hook( __FILE__, array( 'CCB_Core_Activator', 'activate' ) ); +register_deactivation_hook( __FILE__, array( 'CCB_Core_Activator', 'deactivate' ) ); -// internationalization, dashboard-specific hooks, and public-facing site hooks. +// Internationalization, dashboard-specific hooks, and public-facing site hooks. require_once CCB_CORE_PATH . 'includes/class-ccb-core.php'; /** diff --git a/css/ccb-core-admin.css b/css/ccb-core-admin.css index 1a7cdb3..ba93755 100644 --- a/css/ccb-core-admin.css +++ b/css/ccb-core-admin.css @@ -1,21 +1,6 @@ -.test-login-wrapper .button, .test-login-wrapper .spinner, .sync-wrapper .button, .sync-wrapper .spinner { - float: left; - margin-bottom: 20px; -} - -.ccb_core_settings-wrapper div.ajax-message { - width: 50%; - padding: 10px; - clear:both; - font-size: 13px; -} - -.ccb_core_settings-wrapper code { - background: none; - padding: 0; - font-size: 12px; - font-weight: bold; - color: #666666; +.ccb_core_settings-wrapper .spinner { + float: left; + margin-top: 5px; } .ccb_core_settings ul { diff --git a/includes/class-ccb-core-activator.php b/includes/class-ccb-core-activator.php index 97e4607..42462bf 100644 --- a/includes/class-ccb-core-activator.php +++ b/includes/class-ccb-core-activator.php @@ -30,4 +30,17 @@ public static function activate() { // TODO: check dependencies like mcrypt and memory limits. } + /** + * Deactivation code + * + * @since 1.0.0 + */ + public static function deactivate() { + // Ensure we do not have a scheduled hook. + $timestamp = wp_next_scheduled( 'ccb_core_auto_sync_hook' ); + if ( $timestamp ) { + wp_unschedule_event( $timestamp, 'ccb_core_auto_sync_hook' ); + } + } + } diff --git a/includes/class-ccb-core-admin-ajax.php b/includes/class-ccb-core-admin-ajax.php index 0bcb469..52ae986 100644 --- a/includes/class-ccb-core-admin-ajax.php +++ b/includes/class-ccb-core-admin-ajax.php @@ -6,7 +6,7 @@ * @since 1.0.0 * * @package CCB_Core - * @subpackage CCB_Core/admin + * @subpackage CCB_Core/includes */ /** @@ -40,11 +40,11 @@ class CCB_Core_Admin_AJAX { public function __construct() { add_action( 'wp_ajax_sync', array( $this, 'ajax_sync' ) ); add_action( 'wp_ajax_poll_sync', array( $this, 'ajax_poll_sync' ) ); - add_action( 'wp_ajax_test_credentials', array( $this, 'ajax_test_credentials' ) ); add_action( 'wp_ajax_get_latest_sync', array( $this, 'ajax_get_latest_sync' ) ); + add_action( 'wp_ajax_test_credentials', array( $this, 'ajax_test_credentials' ) ); - $this->api = new CCB_Core_API(); - $this->synchronizer = new CCB_Core_Synchronizer(); + $this->api = CCB_Core_API::instance(); + $this->synchronizer = CCB_Core_Synchronizer::instance(); } /** @@ -58,10 +58,10 @@ public function __construct() { public function ajax_sync() { check_ajax_referer( 'ccb_core_nonce', 'nonce' ); - $this->synchronizer->synchronize(); // Tell the user to move along and go about their business... CCB_Core_Helpers::instance()->send_non_blocking_json_success(); + $result = $this->synchronizer->synchronize(); } @@ -76,14 +76,14 @@ public function ajax_sync() { public function ajax_poll_sync() { check_ajax_referer( 'ccb_core_nonce', 'nonce' ); - - //$sync_in_progress = get_transient( $this->plugin_name . '-sync-in-progress' ); - //wp_send_json( array( 'syncInProgress' => $sync_in_progress ) ); + $sync_in_progress = get_transient( CCB_Core_Helpers::SYNC_STATUS_KEY ); + wp_send_json_success( $sync_in_progress ); } /** * Gets the latest synchronization results from an ajax hook + * and encodes / echoes a standardized result array. * * @access public * @since 1.0.0 @@ -93,13 +93,107 @@ public function ajax_get_latest_sync() { check_ajax_referer( 'ccb_core_nonce', 'nonce' ); - //$latest_sync = $this->get_latest_sync_results(); - //wp_send_json( $latest_sync ); + $message = ''; + $result = []; + + // Latest sync results are always stored as an option after a sync takes place. + $latest_sync = get_option( 'ccb_core_latest_sync_result' ); + + if ( ! empty( $latest_sync ) ) { + // Set the success result to the same result as the latest sync. + $result['success'] = $latest_sync['success']; + + if ( true === $latest_sync['success'] ) { + + $message .= esc_html( + sprintf( + // Translators: A formatted date/time. + __( 'The latest synchronization was successful on %s.', 'ccb-core' ), + get_date_from_gmt( + date( 'Y-m-d H:i:s', $latest_sync['timestamp'] ), + get_option( 'date_format' ) . ' \a\t ' . get_option( 'time_format' ) + ) + ) + ) . '
'; + + // Send detailed results for each service that has information. + if ( ! empty( $latest_sync['services'] ) ) { + foreach ( $latest_sync['services'] as $service => $service_result ) { + + $message .= esc_html( + sprintf( + // Translators: The service name. + __( 'Results from the %s service: ', 'ccb-core' ), + $service + ) + ); + + if ( isset( $service_result['insert_update']['processed'] ) ) { + $message .= esc_html( + sprintf( + // Translators: The number of records processed. + __( '%s records inserted / updated. ', 'ccb-core' ), + absint( $service_result['insert_update']['processed'] ) + ) + ); + } + + if ( isset( $service_result['delete']['processed'] ) ) { + $message .= esc_html( + sprintf( + // Translators: The number of records processed. + __( '%s records deleted. ', 'ccb-core' ), + absint( $service_result['delete']['processed'] ) + ) + ); + } + + $message .= '
'; + } + } + + } else { + $message .= esc_html( + sprintf( + __( '%1$s on %2$s', 'ccb-core' ), + $latest_sync['message'], + get_date_from_gmt( + date( 'Y-m-d H:i:s', $latest_sync['timestamp'] ), + get_option( 'date_format' ) . ' \a\t ' . get_option( 'time_format' ) + ) + ) + ) . '
'; + if ( ! empty( $latest_sync['services'] ) ) { + foreach ( $latest_sync['services'] as $service => $service_result ) { + if ( ! empty( $service_result['insert_update']['message'] ) ) { + $message .= $service_result['insert_update']['message'] . '
'; + } + if ( ! empty( $service_result['delete']['message'] ) ) { + $message .= $service_result['delete']['message'] . '
'; + } + } + } + } + } else { + $message .= esc_html__( 'We do not have any recent synchronizations', 'ccb-core' ); + } + + /** + * Filters the message that gets output to the user + * after a synchonrization is finished. + * + * @since 1.0.0 + * + * @param string $message The message with the results. + * @param array $latest_sync The latest synchronization results. + */ + $result['message'] = apply_filters( 'ccb_core_ajax_results_message', $message, $latest_sync ); + wp_send_json_success( $result ); } /** - * Checks the credentials for a user from an ajax hook + * Checks the CCB API credentials for a user from an ajax hook * * @access public * @since 1.0.0 @@ -116,48 +210,6 @@ public function ajax_test_credentials() { } } - /** - * Check if we should schedule a synchronization based on - * the options set by the user - * - * @access public - * @since 1.0.0 - * @return void - */ - public function check_auto_refresh() { - - $settings = get_option( $this->plugin_settings_name ); - - if ( isset( $settings['auto_sync'] ) && 1 === $settings['auto_sync'] ) { - $latest_sync = get_option( $this->plugin_name . '-latest-sync' ); - - if ( ! empty( $latest_sync ) ) { - $auto_sync_timeout = $settings['auto_sync_timeout']; - $now = time(); - $diff = $now - $latest_sync['timestamp']; - - if ( $diff > $auto_sync_timeout * 60 ) { - wp_schedule_single_event( time(), 'schedule_auto_refresh' ); - } - - } else { - wp_schedule_single_event( time(), 'schedule_auto_refresh' ); - } - } - } - - /** - * Callback function to kick off a synchronization - * - * @access public - * @since 1.0.0 - * @return void - */ - public function auto_sync() { - $sync = new CCB_Core_Sync(); - $sync->sync(); - } - } new CCB_Core_Admin_AJAX(); diff --git a/includes/class-ccb-core-api-old.php b/includes/class-ccb-core-api-old.php deleted file mode 100644 index 9b880b5..0000000 --- a/includes/class-ccb-core-api-old.php +++ /dev/null @@ -1,961 +0,0 @@ - - */ -class CCB_Core_API { - - /** - * The subdomain of the ccb church installation - * - * @since 1.0.0 - * @access protected - * @var string $subdomain - */ - protected $subdomain; - - /** - * The ccb api username - * - * @since 1.0.0 - * @access protected - * @var string $username - */ - protected $username; - - /** - * The ccb api password - * - * @since 1.0.0 - * @access protected - * @var string $password - */ - protected $password; - - /** - * The CCB APIs we want to sync with - * - * @since 1.0.0 - * @access protected - * @var array $enabled_apis - */ - protected $enabled_apis = array(); - - /** - * The start date range for calendar events - * - * @since 1.0.0 - * @access protected - * @var string $calendar_start_date - */ - protected $calendar_start_date; - - /** - * The end date range for calendar events - * - * @since 1.0.0 - * @access protected - * @var string $calendar_end_date - */ - protected $calendar_end_date; - - /** - * Any valid service that the core API might integrate with - * - * @since 1.0.0 - * @access protected - * @var array $valid_services - */ - protected $valid_services; - - /** - * Whether or not to additionally import group images - * - * @since 1.0.0 - * @access protected - * @var array $valid_services - */ - protected $import_group_images; - - /** - * Initialize the class and set its properties. - * - * @since 1.0.0 - */ - public function __construct() { - - $settings = get_option( $this->plugin_settings_name ); - - $this->subdomain = $settings['subdomain']; - $this->username = $settings['credentials']['username']; - $this->password = $this->decrypt( $settings['credentials']['password'] ); - - if ( isset( $settings['groups_enabled'] ) && $settings['groups_enabled'] == 1 ) { - - $this->enabled_apis['group_profiles'] = true; - - if ( isset( $settings['groups_import_images'] ) && $settings['groups_import_images'] == 'yes' ) { - $this->import_group_images = true; - } - else { - $this->import_group_images = false; - } - - } - if ( isset( $settings['calendar_enabled'] ) && $settings['calendar_enabled'] == 1 ) { - - $this->enabled_apis['public_calendar_listing'] = true; - - // use sane defaults if this advanced setting isn't set - if ( ! isset( $settings['calendar_date_range_type'] ) ) { - - $this->calendar_start_date = date( 'Y-m-d', strtotime( '1 weeks ago') ); - $this->calendar_end_date = date( 'Y-m-d', strtotime( '+16 weeks' ) ); - - } - elseif ( $settings['calendar_date_range_type'] == 'relative' ) { - - $this->calendar_start_date = date( 'Y-m-d', strtotime( $settings['calendar_relative_weeks_past'] . ' weeks ago') ); - $this->calendar_end_date = date( 'Y-m-d', strtotime( '+' . $settings['calendar_relative_weeks_future'] . ' weeks' ) ); - - } - elseif ( $settings['calendar_date_range_type'] == 'specific' ) { - - // TODO: Use localization for date formats other than U.S. - - if ( $settings['calendar_specific_start'] ) { - - $last_year = strtotime( '1 year ago' ); - $start_timestamp = strtotime( $settings['calendar_specific_start'] ); - - if ( abs( $start_timestamp - $last_year ) > 0 ) { - $this->calendar_start_date = date( 'Y-m-d', $start_timestamp ); - } - else { - $this->calendar_start_date = date( 'Y-m-d', $last_year ); - } - - } - else { - $this->calendar_start_date = date( 'Y-m-d' ); - } - - if ( $settings['calendar_specific_end'] ) { - - $next_year = strtotime( '+1 year' ); - $end_timestamp = strtotime( $settings['calendar_specific_end'] ); - - if ( abs( $next_year - $end_timestamp ) > 0 ) { - $this->calendar_end_date = date( 'Y-m-d', $end_timestamp ); - } - else { - $this->calendar_end_date = date( 'Y-m-d', $next_year ); - } - - } - else { - $this->calendar_end_date = date( 'Y-m-d', strtotime( '+1 year' ) ); - } - } - - } - - $this->valid_services = array( - array( - 'service_name' => 'api_status', - 'service_friendly_name' => 'Credentials', - ), - array( - 'service_name' => 'group_profiles', - 'params' => array( - 'describe_api' => '1' - ), - 'service_friendly_name' => 'Group Profiles API', - ), - array( - 'service_name' => 'public_calendar_listing', - 'params' => array( - 'describe_api' => '1' - ), - 'service_friendly_name' => 'Public Calendar Listing API', - ), - ); - - } - - /** - * Make a service call to the CCB API - * - * The $services array is in the format: - * $services = array( - * array ( - * 'service_name' => 'group_profiles', - * 'params' => array( - * 'modified_since' => '2015-06-01', - * 'include_participants' => 'false' - * ) - * ), - * array ( - * 'service_name' => 'public_calendar_listing', - * 'params' => array( - * 'date_start' => '2015-06-01', - * ) - * ), - * ) - * - * @since 1.0.0 - * @param array $services An array of services and parameters to call - * @access protected - * @return void - */ - protected function call_ccb_api( $services = array() ) { - - set_time_limit(600); - $full_response = array(); - - // for debugging purposes, set a constant and serialize an array like so: - // define( 'RESPONSE_FILE', serialize( array( 'filename' => 'some_file.xml', 'service_name' => 'group_profiles' ) ) ); - // file must be located in the /uploads/ccb-core/ folder - // this will prevent a real api call and will use an xml file - if ( WP_DEBUG == true && defined( 'RESPONSE_FILE' ) ) { - - $service = unserialize( RESPONSE_FILE ); - $upload_dir = wp_upload_dir(); - $filepath = trailingslashit( trailingslashit( $upload_dir['basedir'] ) . $this->plugin_name ) . $service['filename']; - - if ( file_exists( $filepath ) ) { - $response_body = file_get_contents( $filepath ); - libxml_use_internal_errors(true); - $response_xml = simplexml_load_string( $response_body ); - - if ( is_object( $response_xml ) ) { - $full_response['success'] = true; - $full_response[ $service['service_name'] ] = $response_xml; - } - - return $full_response; - } - - } - - if ( ! empty( $services ) && is_array( $services ) ) { - - foreach ( $services as $service ) { - - $params = ''; - if ( isset( $service['params'] ) && ! empty( $service['params'] ) ) { - $params = http_build_query($service['params']); - } - - $api_url = "https://{$this->subdomain}.ccbchurch.com/api.php?srv={$service['service_name']}&{$params}"; - $post_args = array( - 'body' => array(), - 'timeout' => '600', - 'redirection' => '15', - 'httpversion' => '1.0', - 'blocking' => true, - 'headers' => array( - 'Authorization' => 'Basic ' . base64_encode( "{$this->username}:{$this->password}" ) - ), - 'cookies' => array() - ); - - $response = wp_remote_post( $api_url, $post_args ); - $response_code = wp_remote_retrieve_response_code( $response ); - - if ( $response_code != 200 ) { - $full_response['success'] = false; - $full_response['message'] = "There was a problem connecting with the Church Community Builder API - Response Code: {$response_code}"; - break; - } - else { - try { - libxml_use_internal_errors(true); - $response_body = wp_remote_retrieve_body( $response ); - $response_xml = simplexml_load_string( $response_body ); - - if ( is_object( $response_xml ) ) { - $full_response['success'] = true; - $full_response[ $service['service_name'] ] = $response_xml; - } - else { - $full_response['success'] = false; - $full_response['message'] = 'Oops, something went wrong while trying to read the API response. Is your subdomain correct?'; - break; - } - } - catch ( Exception $ex ) { - $full_response['success'] = false; - $full_response['message'] = 'Oops, something went wrong while trying to read the API response. Is your subdomain correct?'; - break; - } - } - - // cache the xml response to the uploads folder if debug mode is on (testing purposes) - if ( WP_DEBUG == true ) { - - $now = new DateTime(); - $cache_filename = $service['service_name'] . '_' . $now->format( 'Y-m-d_His' ) . '.xml'; - $upload_dir = wp_upload_dir(); - - if ( wp_mkdir_p( trailingslashit( $upload_dir['basedir'] ) . $this->plugin_name ) ) { - // first delete any files that weren't created "today" so we don't spam the server over time - $files = preg_grep( '/' . $now->format( 'Y-m-d' ) . '/', glob( trailingslashit( trailingslashit( $upload_dir['basedir'] ) . $this->plugin_name ) . '*' ), PREG_GREP_INVERT ); - foreach ( $files as $file ) { - if ( is_file( $file ) ) { - @unlink( $file ); - } - } - - $upload_file_path = trailingslashit( $upload_dir['basedir'] ) . trailingslashit( $this->plugin_name ) . $cache_filename; - file_put_contents( $upload_file_path, $response_body ); - } - - } - } - } - else { - $full_response['success'] = false; - $full_response['message'] = 'You tried to kick off a syncronization on ' . date( 'F j, Y @ h:i:s a (e)' ) . " but didn't have any integrations enabled (see each service tab)."; - } - - return $full_response; - } - - /** - * Perform a synchronization - * - * @access public - * @since 1.0.0 - * @return void - */ - public function sync() { - - // check for a transient that assumes a sync in in progress - if ( get_transient( $this->plugin_name . '-sync-in-progress' ) ) { - return; - } - else { - set_transient( $this->plugin_name . '-sync-in-progress', true, 60*20 ); - } - - $services = array(); - $current_time = time(); - - // GROUP PROFILES - if ( $this->enabled_apis['group_profiles'] ) { - - $include_participants = false; - $include_participants = apply_filters( 'ccb_include_group_participants', $include_participants ); - - $services[] = array( - 'service_name' => 'group_profiles', - 'params' => array( - 'include_participants' => $include_participants, - ), - ); - - } - - // PUBLIC CALENDAR LISTING - if ( $this->enabled_apis['public_calendar_listing'] ) { - - $services[] = array( - 'service_name' => 'public_calendar_listing', - 'params' => array( 'date_start' => $this->calendar_start_date, 'date_end' => $this->calendar_end_date ), - ); - - } - - $full_response = $this->call_ccb_api( $services ); - $validation_results = $this->validate_response( $full_response ); - // a data structure to hold a unique status of the latest sync results, stored in the db - $latest_sync = array(); - - if ( $validation_results['success'] == true ) { - - // check if any services failed so we can abort the sync and show different messaging - $service_failure = false; - foreach ( $validation_results['services'] as $service ) { - if ( $service['success'] == false ) { - $service_failure = true; - break; - } - } - - if ( $service_failure ) { - - $messages = array(); - - foreach ( $validation_results['services'] as $service ) { - - if ( $service['success'] == false ) { - $messages[] = 'We were not able to successfully synchronize with the ' . $service['service_name'] . ' service on ' . date( 'F j, Y @ h:i:s a (e)', $current_time ) . '. ' . $service['message']; - } - else { - $messages[] = 'We were able to successfully contact the ' . $service['service_name'] . ' service on ' . date( 'F j, Y @ h:i:s a (e)', $current_time ) . ', however we cancelled the synchronization because of other service errors.'; - } - } - - $message = implode( '

', $messages ); - $latest_sync = array( - 'success' => false, - 'message' => $message, - ); - } - else { - - $this->import_cpts( $full_response ); - - $latest_sync = array( - 'success' => true, - 'message' => 'We last successfully synchronized with the Church Community Builder API on ' . date( 'F j, Y @ h:i:s a (e)', $current_time ), - ); - } - } - else { - $latest_sync = array( - 'success' => false, - 'message' => $validation_results['message'] . ' We made the last attempt on ' . date( 'F j, Y @ h:i:s a (e)', $current_time ), - ); - } - - $latest_sync['timestamp'] = $current_time; - delete_transient( $this->plugin_name . '-sync-in-progress' ); - update_option( $this->plugin_name . '-latest-sync', $latest_sync ); - - } - - /** - * Tests API connection, credentials, and specific services - * as defined in the constructor - * - * @access public - * @since 1.0.0 - * @return string - */ - public function test_api_credentials() { - - $full_response = $this->call_ccb_api( $this->valid_services ); - delete_transient( $this->plugin_name . '-sync-in-progress' ); - return $this->validate_response( $full_response ); - - } - - /** - * Takes a CCB API response and parses it for basic business rules. - * Returns an array of successes, failures, and messages - * - * @param mixed $full_response - * @access protected - * @since 1.0.0 - * @return array - */ - protected function validate_response( $full_response ) { - - $validation_results = array(); - - if ( $full_response['success'] ) { - - $validation_results['success'] = true; - $validation_results['services'] = array(); - - foreach ( $this->valid_services as $service ) { - - if ( ! empty ( $full_response[ $service['service_name'] ] ) ) { - - $result_array = array(); - - if ( isset( $full_response[ $service['service_name'] ]->response->errors ) ) { - if ( isset( $full_response[ $service['service_name'] ]->response->errors->error ) && ! empty( $full_response[ $service['service_name'] ]->response->errors->error ) ) { - $result_array = array( - 'success' => false, - 'label' => $service['service_friendly_name'], - 'service_name' => $service['service_name'], - 'message' => 'The API responded with the message:
"' . $full_response[ $service['service_name'] ]->response->errors->error . '"', - ); - } - else { - $result_array = array( - 'success' => false, - 'label' => $service['service_friendly_name'], - 'service_name' => $service['service_name'], - 'message' => 'The API did not provide any other information', - ); - } - } - else { - $result_array = array( - 'success' => true, - 'label' => $service['service_friendly_name'], - 'service_name' => $service['service_name'], - 'message' => 'Success', - ); - } - - $validation_results['services'][] = $result_array; - } - - } - - } - else { - // the entire call was a failure. a sad sad failure. - $validation_results['success'] = false; - $validation_results['message'] = $full_response['message']; - } - - return $validation_results; - } - - /** - * Parses the XML response, deletes existing CPTs, and imports CCB data - * - * @param mixed $full_response - * @since 1.0.0 - * @access protected - * @return void - */ - protected function import_cpts( $full_response ) { - - global $wpdb; - // temporarily disable counting for performance - wp_defer_term_counting( true ); - wp_defer_comment_counting( true ); - // temporarily disable autocommit - $wpdb->query( 'SET autocommit = 0;' ); - - - // GROUP PROFILES - if ( $this->enabled_apis['group_profiles'] == true && isset( $full_response['group_profiles']->response->groups->group ) && ! empty( $full_response['group_profiles']->response->groups->group ) ) { - - $groups_taxonomy_map = CCB_Core_CPTs::get_groups_taxonomy_map(); - $groups_taxonomy_map = apply_filters( 'ccb_get_groups_taxonomy_map', $groups_taxonomy_map ); - - $groups_custom_fields_map = CCB_Core_CPTs::get_groups_custom_fields_map(); - $groups_custom_fields_map = apply_filters( 'ccb_get_groups_custom_fields_map', $groups_custom_fields_map ); - - // delete the existing taxonomy terms - foreach ( $groups_taxonomy_map as $taxonomy_name => $taxonomy ) { - $terms = get_terms( $taxonomy_name, array( 'fields' => 'ids', 'hide_empty' => false ) ); - if ( ! empty( $terms ) ) { - foreach ( $terms as $term_value ) { - wp_delete_term( $term_value, $taxonomy_name ); - } - } - } - - // delete existing custom posts - $custom_posts = get_posts( array( 'post_type' => $this->plugin_name . '-groups', 'posts_per_page' => -1 ) ); - foreach( $custom_posts as $custom_post ) { - - // delete the post thumbnail if it exists before deleting the post - $thumbnail_id = get_post_thumbnail_id( $custom_post->ID ); - if ( $thumbnail_id ) { - wp_delete_attachment( $thumbnail_id, true ); - } - - wp_delete_post( $custom_post->ID, true); - } - - // commit the deletes now - $wpdb->query( 'COMMIT;' ); - - // keep track of whether or not a default image has already been imported - $default_attachment = 0; - - foreach ( $full_response['group_profiles']->response->groups->group as $group ) { - - // only allow publicly listed and active groups to be imported - if ( $group->inactive == 'false' && $group->public_search_listed == 'true' ) { - - $group_id = 0; - foreach( $group->attributes() as $key => $value ) { - if ( $key == 'id' ) { - $group_id = (int) $value; - break; - } - } - - // insert group post - $group_post_atts = array( - 'post_title' => $group->name, - 'post_name' => $group->name, - 'post_content' => $group->description, - 'post_status' => 'publish', - 'post_type' => $this->plugin_name . '-groups', - ); - $post_id = wp_insert_post( $group_post_atts ); - - // insert hierarchial taxonomy values (categories) and non-hierarchial taxonomy values (tags) - $taxonomy_atts = $this->get_taxonomy_atts( $group, $groups_taxonomy_map ); - if ( ! empty( $taxonomy_atts ) ) { - foreach ( $taxonomy_atts as $taxonomy_attribute ) { - wp_set_post_terms( $post_id, $taxonomy_attribute['terms'], $taxonomy_attribute['taxonomy'], true ); - } - } - - // insert custom fields - $custom_fields_atts = $this->get_custom_fields_atts( $group, $groups_custom_fields_map ); - if ( ! empty( $custom_fields_atts ) ) { - foreach ( $custom_fields_atts as $field_key => $custom_fields_attribute ) { - add_post_meta( $post_id, $custom_fields_attribute['field_name'], $custom_fields_attribute['field_value'] ); - } - } - - // download and attach the group image as the featured image - if ( isset( $group->image ) && $this->import_group_images == true ) { - - $group_image_url = esc_url_raw( $group->image ); - - if ( ! empty( $group_image_url ) ) { - - // handle default images - if ( strpos( $group_image_url, 'default' ) ) { - if ( ! $default_attachment ) { - $attachment_result = $this->create_media_image( 'default', 0, $group_image_url ); - if ( $attachment_result ) { - $default_attachment = $attachment_result; - set_post_thumbnail( $post_id, $default_attachment ); - } - } - else { - set_post_thumbnail( $post_id, $default_attachment ); - } - } - else { - $attachment_result = $this->create_media_image( $group->name, $post_id, $group_image_url ); - if ( $attachment_result ) { - set_post_thumbnail( $post_id, $attachment_result ); - } - } - } - - } - - } - - } - - // commit the inserts now - $wpdb->query( 'COMMIT;' ); - - } - - // PUBLIC CALENDAR LISTING - if ( $this->enabled_apis['public_calendar_listing'] == true && isset( $full_response['public_calendar_listing']->response->items->item ) && ! empty( $full_response['public_calendar_listing']->response->items->item ) ) { - - $calendar_taxonomy_map = CCB_Core_CPTs::get_calendar_taxonomy_map(); - $calendar_taxonomy_map = apply_filters( 'ccb_get_calendar_taxonomy_map', $calendar_taxonomy_map ); - - $calendar_custom_fields_map = CCB_Core_CPTs::get_calendar_custom_fields_map(); - $calendar_custom_fields_map = apply_filters( 'ccb_get_calendar_custom_fields_map', $calendar_custom_fields_map ); - - // delete the existing taxonomy terms - foreach ( $calendar_taxonomy_map as $taxonomy_name => $taxonomy ) { - $terms = get_terms( $taxonomy_name, array( 'fields' => 'ids', 'hide_empty' => false ) ); - if ( ! empty( $terms ) ) { - foreach ( $terms as $term_value ) { - wp_delete_term( $term_value, $taxonomy_name ); - } - } - } - - // delete existing custom posts - $custom_posts = get_posts( array( 'post_type' => $this->plugin_name . '-calendar', 'posts_per_page' => -1 ) ); - foreach( $custom_posts as $custom_post ) { - wp_delete_post( $custom_post->ID, true); - } - - // commit the deletes now - $wpdb->query( 'COMMIT;' ); - - foreach ( $full_response['public_calendar_listing']->response->items->item as $event ) { - - // insert event post - $event_post_atts = array( - 'post_title' => $event->event_name, - 'post_name' => $event->event_name, - 'post_content' => $event->event_description, - 'post_status' => 'publish', - 'post_type' => $this->plugin_name . '-calendar', - ); - $post_id = wp_insert_post( $event_post_atts ); - - // insert hierarchial taxonomy values (categories) and non-hierarchial taxonomy values (tags) - $taxonomy_atts = $this->get_taxonomy_atts( $event, $calendar_taxonomy_map ); - if ( ! empty( $taxonomy_atts ) ) { - foreach ( $taxonomy_atts as $taxonomy_attribute ) { - wp_set_post_terms( $post_id, $taxonomy_attribute['terms'], $taxonomy_attribute['taxonomy'], true ); - } - } - - // insert custom fields - $custom_fields_atts = $this->get_custom_fields_atts( $event, $calendar_custom_fields_map ); - if ( ! empty( $custom_fields_atts ) ) { - foreach ( $custom_fields_atts as $field_key => $custom_fields_attribute ) { - add_post_meta( $post_id, $custom_fields_attribute['field_name'], $custom_fields_attribute['field_value'] ); - } - } - - } - - // commit the inserts now - $wpdb->query( 'COMMIT;' ); - - } - - // re-enable autocommit - $wpdb->query( 'SET autocommit = 1;' ); - // re-enable counting - wp_defer_term_counting( false ); - wp_defer_comment_counting( false ); - - } - - /** - * Uses a taxonomy map to build out the categories and tags - * for a CCB custom post type - * - * @param mixed $post_data - * @param array $taxonomy_map - * @access protected - * @since 1.0.0 - * @return void - */ - protected function get_taxonomy_atts( $post_data, $taxonomy_map ) { - - foreach( $taxonomy_map as $taxonomy_name => $taxonomy ) { - - if ( $taxonomy['hierarchical'] ) { - - $taxonomy_value = (string) $post_data->$taxonomy['api_mapping']; - - if ( ! empty( $taxonomy_value ) ) { - - $term_id = term_exists( $taxonomy_value, $taxonomy_name ); - if ( $term_id ) { - $terms_collection[] = array( - 'terms' => $term_id['term_id'], - 'taxonomy' => $taxonomy_name, - ); - } - else { - $new_term = wp_insert_term( $taxonomy_value, $taxonomy_name ); - $terms_collection[] = array( - 'terms' => $new_term['term_id'], - 'taxonomy' => $taxonomy_name, - ); - } - - } - - } - else { - - if ( isset( $taxonomy['api_mapping'] ) && ! empty( $taxonomy['api_mapping'] ) ) { - - foreach ( $taxonomy['api_mapping'] as $api_mapping => $tag_name ) { - - $tag_value = ( $post_data->$api_mapping == 'true' ? $tag_name : false ); - if ( $tag_value ) { - $terms_collection[] = array( - 'terms' => $tag_value, - 'taxonomy' => $taxonomy_name, - ); - } - - } - - } - } - - } - - return $terms_collection; - - } - - /** - * Uses a custom fields map to build out the custom fields - * for a CCB custom post type - * - * @param mixed $post_data - * @param array $custom_fields_map - * @param string $parent_field_name - * @access protected - * @since 1.0.0 - * @return void - */ - protected function get_custom_fields_atts( $post_data, $custom_fields_map, $parent_field_name = '' ) { - - $custom_fields_collection = array(); - - foreach( $custom_fields_map as $field_name => $field_data ) { - - switch ( $field_data['data_type'] ) { - case 'string': - $field_value = (string) $post_data->$field_data['api_mapping']; - $custom_fields_collection[] = array( - 'field_name' => $field_name, - 'field_value' => $field_value, - ); - break; - case 'int': - $field_value = (int) $post_data->$field_data['api_mapping']; - $custom_fields_collection[] = array( - 'field_name' => $field_name, - 'field_value' => $field_value, - ); - break; - case 'object': - if ( isset( $field_data['child_object'] ) && ! empty( $field_data['child_object'] ) ) { - // some child objects may be collections of objects - if ( count( $post_data->$field_data['api_mapping'] ) > 1 ) { - $collection_grouping = array(); - foreach ( $post_data->$field_data['api_mapping'] as $key => $child_field_data ) { - if ( is_object ( $child_field_data ) ) { - $collection_grouping[] = $this->get_custom_fields_atts( $child_field_data, $field_data['child_object'], $field_name ); - } - } - $prepared_field_collection = $this->prepare_field_collection( $parent_field_name, $collection_grouping ); - $custom_fields_collection[] = $prepared_field_collection; - } - else { - $child_custom_fields_collection = $this->get_custom_fields_atts( $post_data->$field_data['api_mapping'], $field_data['child_object'], $field_name ); - $custom_fields_collection = array_merge( $custom_fields_collection, $child_custom_fields_collection ); - } - } - break; - } - - } - - return $custom_fields_collection; - - } - - /** - * Takes a multidimensional array of field collections and formats - * them into groups that are compatible with storage in a - * single custom field - * - * @param string $field_name - * @param array $collection_grouping - * @access protected - * @since 0.9.4 - * @return array - */ - protected function prepare_field_collection( $field_name, $collection_grouping ) { - - $flat_collection = array( - 'field_name' => $field_name, - 'field_value' => array(), - ); - - foreach( $collection_grouping as $grouping_value ) { - - $grouping_array = array(); - - foreach ( $grouping_value as $value_pair ) { - - $grouping_array[] = array( - $value_pair['field_name'] => $value_pair['field_value'] - ); - - } - - $flat_collection['field_value'][] = $grouping_array; - - } - - return $flat_collection; - } - - /** - * Downloads an image from a URL, uploads it to the Media Library, - * and then optionally attaches it to a post - * - * @param string $group_name - * @param int $post_id - * @param string $image_url - * @access protected - * @since 0.9.5 - * @return mixed Returns a media id or false on failure - */ - protected function create_media_image( $group_name, $post_id, $image_url ) { - - // fetch the image from the cdn and store temporarily - $temp_file = download_url( $image_url ); - - if ( is_wp_error( $temp_file ) ) { - return false; - } - - // attempt to detect the mimetype based on the available functions - $extension = false; - if ( function_exists( 'exif_imagetype' ) && function_exists( 'image_type_to_extension' ) ) { - // open with exif - $image_type = exif_imagetype( $temp_file ); - if ( $image_type ) { - $extension = image_type_to_extension( $image_type ); - } - } - elseif ( function_exists( 'getimagesize' ) && function_exists( 'image_type_to_extension' ) ) { - // open with gd - $file_size = getimagesize( $temp_file ); - if ( isset( $file_size[2] ) ) { - $extension = image_type_to_extension( $file_size[2] ); - } - } - elseif ( function_exists( 'finfo_open' ) ) { - // open with fileinfo - $resource = finfo_open( FILEINFO_MIME_TYPE ); - $mimetype = finfo_file( $resource, $temp_file ); - finfo_close( $resource ); - if ( $mimetype ) { - $mimetype_array = explode( '/', $mimetype ); - $extension = '.' . $mimetype_array[1]; - } - } - - if ( $extension ) { - - $filename = 'ccb-' . sanitize_file_name( strtolower( $group_name ) ) . $extension; - - $file_array = array( - 'name' => $filename, - 'tmp_name' => $temp_file, - ); - - $media_id = media_handle_sideload( $file_array, $post_id ); - @unlink( $temp_file ); - - if ( is_wp_error( $media_id ) ) { - return false; - } - - return $media_id; - - } - else { - return false; - } - } - -} diff --git a/includes/class-ccb-core-api.php b/includes/class-ccb-core-api.php index 18e3261..722197b 100644 --- a/includes/class-ccb-core-api.php +++ b/includes/class-ccb-core-api.php @@ -28,39 +28,74 @@ class CCB_Core_API { */ public $initialized = false; + /** + * Instance of the class + * + * @var CCB_Core_API + * @access private + * @static + */ + private static $instance; + /** * The subdomain of the ccb church installation * * @since 1.0.0 - * @access protected + * @access private * @var string $subdomain */ - protected $subdomain; + private $subdomain; /** * The ccb api username * * @since 1.0.0 - * @access protected + * @access private * @var string $username */ - protected $username; + private $username; /** * The ccb api password * * @since 1.0.0 - * @access protected + * @access private * @var string $password */ - protected $password; + private $password; /** - * Initialize the class and set its properties. + * Unused constructor in the singleton pattern * - * @since 1.0.0 + * @access public + * @return void */ public function __construct() { + // Initialize this class with the instance() method. + } + + /** + * Returns the instance of the class + * + * @access public + * @static + * @return CCB_Core_API + */ + public static function instance() { + if ( ! isset( static::$instance ) ) { + static::$instance = new CCB_Core_API(); + static::$instance->setup(); + } + return static::$instance; + } + + /** + * Initial setup of the singleton + * + * @access private + * @return void + */ + private function setup() { // Wait to initialize the API credentials until after WordPress // has loaded pluggable.php because we are using some WordPress helper functions. add_action( 'plugins_loaded', [ $this, 'initialize_credentials' ] ); @@ -209,6 +244,7 @@ private function request( $method, $service, $data = array() ) { return $result; } + // Serialize the response XML into a SimpleXML object. try { libxml_use_internal_errors( true ); $parsed_response = simplexml_load_string( $result['xml'] ); @@ -241,3 +277,5 @@ private function request( $method, $service, $data = array() ) { } } + +CCB_Core_API::instance(); diff --git a/includes/class-ccb-core-cron.php b/includes/class-ccb-core-cron.php new file mode 100644 index 0000000..24222e6 --- /dev/null +++ b/includes/class-ccb-core-cron.php @@ -0,0 +1,113 @@ + + */ +class CCB_Core_Cron { + + /** + * An instance of the CCB_Core_Synchronizer class + * + * @var CCB_Core_Synchronizer + */ + private $synchronizer; + + /** + * Initialize the class and set its properties. + * + * @since 1.0.0 + */ + public function __construct() { + // Setup a custom cron schedule based on the user preferences. + // phpcs:ignore + add_filter( 'cron_schedules', [ $this, 'custom_cron_schedule' ] ); + // Setup the action and callback for the hook. + add_action( 'ccb_core_auto_sync_hook', [ $this, 'auto_sync_callback' ] ); + // When the cron settings are changed, configure the events. + add_action( 'update_option_ccb_core_settings', [ $this, 'cron_settings_changed' ], 10, 2 ); + + $this->synchronizer = CCB_Core_Synchronizer::instance(); + } + + /** + * Create a custom cron schedule based on the + * timeout interval set by the user. + * + * @param array $schedules An array of cron schedules. + * @return array + */ + public function custom_cron_schedule( $schedules ) { + $settings = CCB_Core_Helpers::instance()->get_options(); + if ( ! empty( $settings['auto_sync_timeout'] ) ) { + $schedules['ccb_core_schedule'] = [ + 'interval' => MINUTE_IN_SECONDS * absint( $settings['auto_sync_timeout'] ), + 'display' => esc_html__( sprintf( + __( 'Every %s Minutes' ), + absint( $settings['auto_sync_timeout'] ) + ) ), + ]; + } + return $schedules; + } + + /** + * Callback method to detect when the settings have changed. + * + * We check for whether or not the auto sync was turned on / off and + * whether or not the user changed the timeout interval. This is + * how we register cron events and clean up invalid events. + * + * @param array $old_value The old settings array. + * @param array $new_value The new settings array. + * @return void + */ + public function cron_settings_changed( $old_value, $new_value ) { + // If the cron was enabled OR the timeout was changed. + if ( + ( '' === $old_value['auto_sync'] && '1' === $new_value['auto_sync'] ) + || ( $old_value['auto_sync_timeout'] !== $new_value['auto_sync_timeout'] ) + ) { + $this->remove_existing_cron_events(); + wp_schedule_event( time(), 'ccb_core_schedule', 'ccb_core_auto_sync_hook' ); + } elseif ( '1' === $old_value['auto_sync'] && '' === $new_value['auto_sync'] ) { + $this->remove_existing_cron_events(); + } + } + + /** + * Removes all CCB Core cron events. + * + * @return void + */ + private function remove_existing_cron_events() { + $timestamp = wp_next_scheduled( 'ccb_core_auto_sync_hook' ); + if ( $timestamp ) { + wp_unschedule_event( $timestamp, 'ccb_core_auto_sync_hook' ); + } + } + + /** + * The callback method of the cron event that kicks off a synchronization + * + * @return void + */ + public function auto_sync_callback() { + $this->synchronizer->synchronize(); + } + +} + +new CCB_Core_Cron(); diff --git a/includes/class-ccb-core-helpers.php b/includes/class-ccb-core-helpers.php index 1dda450..c46e372 100644 --- a/includes/class-ccb-core-helpers.php +++ b/includes/class-ccb-core-helpers.php @@ -20,13 +20,13 @@ */ class CCB_Core_Helpers { - const SYNC_STATUS_KEY = 'ccb-core-sync-in-progress'; + const SYNC_STATUS_KEY = 'ccb_core_sync_in_progress'; /** * Instance of the Helper class * - * @var CCB_Core_Helpers - * @access protected + * @var CCB_Core_Helpers + * @access private * @static */ private static $instance; @@ -34,15 +34,15 @@ class CCB_Core_Helpers { /** * The options set by the user * - * @var array + * @var array */ private $plugin_options = array(); /** * Unused constructor in the singleton pattern * - * @access public - * @return void + * @access public + * @return void */ public function __construct() { // Initialize this class with the instance() method. @@ -51,9 +51,9 @@ public function __construct() { /** * Returns the instance of the class * - * @access public + * @access public * @static - * @return CCB_Core_Helpers + * @return CCB_Core_Helpers */ public static function instance() { if ( ! isset( static::$instance ) ) { @@ -66,8 +66,8 @@ public static function instance() { /** * Initial setup of the singleton * - * @access private - * @return void + * @access private + * @return void */ private function setup() { // Get any options the user may have set. @@ -77,7 +77,7 @@ private function setup() { /** * Get any options stored by the user * - * @return array + * @return array */ public function get_options() { return $this->plugin_options; @@ -141,9 +141,10 @@ public function decrypt( $data ) { * * @access public * @since 1.0.0 + * @param array $data Optional data to send back. * @return bool */ - public function send_non_blocking_json_success() { + public function send_non_blocking_json_success( $data = array() ) { ignore_user_abort( true ); ob_start(); @@ -151,7 +152,10 @@ public function send_non_blocking_json_success() { header( 'Content-Type: application/json' ); header( 'Content-Encoding: none' ); - echo wp_json_encode( [ 'success' => true ] ); + echo wp_json_encode( [ + 'success' => true, + 'data' => $data, + ] ); header( 'Connection: close' ); header( 'Content-Length: ' . ob_get_length() ); @@ -237,6 +241,7 @@ public function download_image( $image_url, $filename = '', $post_id = 0 ) { set_post_thumbnail( $post_id, $media_id ); } + // phpcs:ignore @unlink( $temp_file ); if ( ! is_wp_error( $media_id ) ) { @@ -257,8 +262,14 @@ public function download_image( $image_url, $filename = '', $post_id = 0 ) { * @return array */ public function custom_uploads_directory( $upload ) { - // Allow for the ability to enable / disable custom upload path. - if ( apply_filters( 'ccb_core_allow_custom_upload_directory', true ) ) { + /** + * Allow for the ability to enable / disable custom upload path. + * + * @since 1.0.0 + * + * @param bool $allowed Whether this plugin is allowed to use custom upload paths. + */ + if ( apply_filters( 'ccb_core_allow_custom_uploads_directory', true ) ) { $upload['path'] = trailingslashit( $upload['basedir'] ) . 'ccb'; $upload['url'] = $upload['baseurl'] . '/ccb'; $upload['subdir'] = '/ccb'; diff --git a/includes/class-ccb-core-plugin.php b/includes/class-ccb-core-plugin.php deleted file mode 100644 index dbc1254..0000000 --- a/includes/class-ccb-core-plugin.php +++ /dev/null @@ -1,146 +0,0 @@ - - */ -class CCB_Core_Plugin { - - /** - * The unique identifier of this plugin. - * - * @since 0.9.0 - * @access protected - * @var string $plugin_name The string used to uniquely identify this plugin. - */ - protected $plugin_name; - - /** - * The settings variable name used to access the plugin settings - * - * @since 0.9.0 - * @access protected - * @var string $plugin_settings_name - */ - protected $plugin_settings_name; - - /** - * The display name of this plugin. - * - * @since 0.9.0 - * @access protected - * @var string $plugin_display_name The display name of this plugin. - */ - protected $plugin_display_name; - - /** - * The short display name of this plugin. - * - * @since 0.9.0 - * @access protected - * @var string $plugin_short_display_name The short display name of this plugin. - */ - protected $plugin_short_display_name; - - /** - * The current version of the plugin. - * - * @since 0.9.0 - * @access protected - * @var string $version The current version of the plugin. - */ - protected $version; - - /** - * Define the core properties of the plugin - * - * Set the plugin name and the plugin version that can be used throughout the plugin. - * - * @since 0.9.0 - */ - public function __construct() { - - $this->plugin_name = 'ccb-core'; - $this->plugin_settings_name = 'ccb_core_settings'; - $this->plugin_display_name = __( 'Church Community Builder Core API', $this->plugin_name ); - $this->plugin_short_display_name = __( 'CCB Core API', $this->plugin_name ); - $this->version = '0.9.6'; - add_theme_support( 'post-thumbnails' ); - - } - - /** - * Helper function to check if a date is valid - * - * @param string $date The date. - * @param string $format - * @access protected - * @since 0.9.0 - * @return bool - */ - protected function valid_date( $date, $format = 'Y-m-d H:i:s' ) { - $version = explode('.', phpversion()); - if ( (int) $version[0] >= 5 && (int) $version[1] >= 2 && (int) $version[2] > 17 ) { - $d = DateTime::createFromFormat( $format, $date ); - } else { - $d = new DateTime( date( $format, strtotime( $date ) ) ); - } - return $d && $d->format( $format ) == $date; - } - - /** - * Gets the most recent synchronization results in the form - * of an array with a style class and message - * - * @access protected - * @since 0.9.0 - * @return array - */ - protected function get_latest_sync_results() { - - $latest_sync = get_option( $this->plugin_name . '-latest-sync' ); - - if ( is_array( $latest_sync ) && ! empty( $latest_sync ) ) { - - if ( $latest_sync['success'] == true ) { - - $latest_sync_message = array( - 'style' => 'updated', - 'description' => $latest_sync['message'], - ); - - } - else { - $latest_sync_message = array( - 'style' => 'error', - 'description' => $latest_sync['message'], - ); - } - } - else { - $latest_sync_message = array( - 'style' => 'notice', - 'description' => "It looks like you haven't synchronized anything yet." - ); - } - - return $latest_sync_message; - - } - -} - diff --git a/includes/class-ccb-core-settings-field.php b/includes/class-ccb-core-settings-field.php index bd6ca7b..a3587de 100644 --- a/includes/class-ccb-core-settings-field.php +++ b/includes/class-ccb-core-settings-field.php @@ -6,14 +6,14 @@ * @since 0.9.0 * * @package CCB_Core - * @subpackage CCB_Core/admin + * @subpackage CCB_Core/includes */ /** * Object to manage the plugin settings fields * * @package CCB_Core - * @subpackage CCB_Core/admin + * @subpackage CCB_Core/includes * @author Jared Cobb */ class CCB_Core_Settings_Field { @@ -276,9 +276,8 @@ protected function render_test_credentials() { ); } else { echo sprintf( - '