Content Control
Signed-off-by: Jeff <jeff@gotenzing.com>
Showing
190 changed files
with
4891 additions
and
0 deletions
| 1 | <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
| 2 | <path d="M21.8611 8.50354L19.7251 10.6395C20.1421 11.16 20.4841 11.6355 20.7286 12C19.5886 13.695 16.4536 17.73 12.3871 17.9775L9.66455 20.7C10.4086 20.886 11.1856 21 12.0001 21C19.0606 21 23.6161 13.074 23.8066 12.738C24.0631 12.282 24.0646 11.724 23.8081 11.268C23.7376 11.1405 23.0581 9.94654 21.8611 8.50354Z" fill="red"/> | ||
| 3 | <path d="M0.439696 23.5606C0.732196 23.8531 1.1162 24.0001 1.5002 24.0001C1.8842 24.0001 2.2682 23.8531 2.5607 23.5606L23.5607 2.56063C24.1472 1.97413 24.1472 1.02613 23.5607 0.439631C22.9742 -0.146869 22.0262 -0.146869 21.4397 0.439631L17.3222 4.55713C15.7727 3.64663 13.9967 3.00013 12.0002 3.00013C4.8677 3.00013 0.376696 10.9336 0.189196 11.2711C-0.0643035 11.7256 -0.0628035 12.2791 0.192196 12.7336C0.297196 12.9211 1.7582 15.4366 4.2317 17.6476L0.438196 21.4411C-0.146804 22.0261 -0.146804 22.9741 0.439696 23.5606ZM3.2717 11.9986C4.4372 10.2526 7.7192 6.00013 12.0002 6.00013C13.1132 6.00013 14.1557 6.30163 15.1172 6.76213L12.7682 9.11113C12.5222 9.04363 12.2672 9.00013 12.0002 9.00013C10.3427 9.00013 9.0002 10.3426 9.0002 12.0001C9.0002 12.2671 9.0437 12.5221 9.1112 12.7681L6.3602 15.5191C4.9277 14.2651 3.8387 12.8431 3.2717 11.9986Z" fill="red"/> | ||
| 4 | </svg> |
This diff is collapsed.
Click to expand it.
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="none"> | ||
| 3 | <path d="M13 2C6.92487 2 2 6.92487 2 13C2 15.249 2.67495 17.3404 3.83333 19.0826M20.3333 4.80094C22.5837 6.81512 24 9.74217 24 13C24 19.0751 19.0751 24 13 24C11.7143 24 10.4802 23.7794 9.33333 23.3741M13 9.33333V16.6667" stroke="#007CBA" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"></path> | ||
| 4 | </svg> |
12.5 KB
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Plugin container. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Base; | ||
| 11 | |||
| 12 | defined( 'ABSPATH' ) || exit; | ||
| 13 | |||
| 14 | use ContentControl\Vendor\Pimple\Container as Base; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Localized container class. | ||
| 18 | */ | ||
| 19 | class Container extends Base { | ||
| 20 | /** | ||
| 21 | * Get item from container | ||
| 22 | * | ||
| 23 | * @param string $id Key for the item. | ||
| 24 | * | ||
| 25 | * @return mixed Current value of the item. | ||
| 26 | */ | ||
| 27 | public function get( $id ) { | ||
| 28 | return $this->offsetGet( $id ); | ||
| 29 | } | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Set item in container | ||
| 33 | * | ||
| 34 | * @param string $id Key for the item. | ||
| 35 | * @param mixed $value Value to be set. | ||
| 36 | * | ||
| 37 | * @return void | ||
| 38 | */ | ||
| 39 | public function set( $id, $value ) { | ||
| 40 | $this->offsetSet( $id, $value ); | ||
| 41 | } | ||
| 42 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Plugin controller. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Base; | ||
| 11 | |||
| 12 | defined( 'ABSPATH' ) || exit; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Localized container class. | ||
| 16 | */ | ||
| 17 | abstract class Controller implements \ContentControl\Interfaces\Controller { | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Plugin Container. | ||
| 21 | * | ||
| 22 | * @var \ContentControl\Plugin\Core | ||
| 23 | */ | ||
| 24 | public $container; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Initialize based on dependency injection principles. | ||
| 28 | * | ||
| 29 | * @param \ContentControl\Plugin\Core $container Plugin container. | ||
| 30 | * @return void | ||
| 31 | */ | ||
| 32 | public function __construct( $container ) { | ||
| 33 | $this->container = $container; | ||
| 34 | } | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Check if controller is enabled. | ||
| 38 | * | ||
| 39 | * @return bool | ||
| 40 | */ | ||
| 41 | public function controller_enabled() { | ||
| 42 | return true; | ||
| 43 | } | ||
| 44 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Plugin controller. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Base; | ||
| 11 | |||
| 12 | defined( 'ABSPATH' ) || exit; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * HTTP Stream class. | ||
| 16 | */ | ||
| 17 | class Stream { | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Stream name. | ||
| 21 | * | ||
| 22 | * @var string | ||
| 23 | */ | ||
| 24 | protected $stream_name; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Version. | ||
| 28 | * | ||
| 29 | * @var string | ||
| 30 | */ | ||
| 31 | const VERSION = '1.0.0'; | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Stream constructor. | ||
| 35 | * | ||
| 36 | * @param string $stream_name Stream name. | ||
| 37 | */ | ||
| 38 | public function __construct( $stream_name = 'stream' ) { | ||
| 39 | $this->stream_name = $stream_name; | ||
| 40 | } | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Start SSE stream. | ||
| 44 | * | ||
| 45 | * @return void | ||
| 46 | */ | ||
| 47 | public function start() { | ||
| 48 | if ( headers_sent() ) { | ||
| 49 | // Do not start the stream if headers have already been sent. | ||
| 50 | return; | ||
| 51 | } | ||
| 52 | |||
| 53 | // Disable default disconnect checks. | ||
| 54 | ignore_user_abort( true ); | ||
| 55 | |||
| 56 | // phpcs:disable WordPress.PHP.IniSet.Risky, WordPress.PHP.NoSilencedErrors.Discouraged | ||
| 57 | @ini_set( 'zlib.output_compression', '0' ); | ||
| 58 | @ini_set( 'implicit_flush', '1' ); | ||
| 59 | @ini_set( 'log_limit', '8096' ); | ||
| 60 | |||
| 61 | @ob_end_clean(); | ||
| 62 | set_time_limit( 0 ); | ||
| 63 | // phpcs:enable WordPress.PHP.IniSet.Risky, WordPress.PHP.NoSilencedErrors.Discouraged | ||
| 64 | |||
| 65 | $this->send_headers(); | ||
| 66 | } | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Send SSE headers. | ||
| 70 | * | ||
| 71 | * @return void | ||
| 72 | */ | ||
| 73 | public function send_headers() { | ||
| 74 | header( 'Content-Type: text/event-stream' ); | ||
| 75 | header( 'Stream-Name: ' . $this->stream_name ); | ||
| 76 | header( 'Cache-Control: no-cache' ); | ||
| 77 | header( 'Connection: keep-alive' ); | ||
| 78 | // Nginx: unbuffered responses suitable for Comet and HTTP streaming applications. | ||
| 79 | header( 'X-Accel-Buffering: no' ); | ||
| 80 | $this->flush_buffers(); | ||
| 81 | } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Flush buffers. | ||
| 85 | * | ||
| 86 | * Uses a micro delay to prevent the stream from flushing too quickly. | ||
| 87 | * | ||
| 88 | * @return void | ||
| 89 | */ | ||
| 90 | protected function flush_buffers() { | ||
| 91 | // This is for the buffer achieve the minimum size in order to flush data. | ||
| 92 | |||
| 93 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | ||
| 94 | echo str_repeat( ' ', 1024 * 8 ) . PHP_EOL; | ||
| 95 | |||
| 96 | flush(); // Unless both are called. Some browsers will still cache. | ||
| 97 | |||
| 98 | // Neccessary to prevent the stream from flushing too quickly. | ||
| 99 | usleep( 1000 ); | ||
| 100 | } | ||
| 101 | |||
| 102 | /** | ||
| 103 | * Send general message/data to the client. | ||
| 104 | * | ||
| 105 | * @param mixed $data Data to send. | ||
| 106 | * | ||
| 107 | * @return void | ||
| 108 | */ | ||
| 109 | public function send_data( $data ) { | ||
| 110 | $data = is_string( $data ) ? $data : \wp_json_encode( $data ); | ||
| 111 | |||
| 112 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | ||
| 113 | echo "data: {$data}" . PHP_EOL; | ||
| 114 | echo PHP_EOL; | ||
| 115 | |||
| 116 | $this->flush_buffers(); | ||
| 117 | } | ||
| 118 | |||
| 119 | /** | ||
| 120 | * Send an event to the client. | ||
| 121 | * | ||
| 122 | * @param string $event Event name. | ||
| 123 | * @param mixed $data Data to send. | ||
| 124 | * | ||
| 125 | * @return void | ||
| 126 | */ | ||
| 127 | public function send_event( $event, $data = '' ) { | ||
| 128 | $data = is_string( $data ) ? $data : \wp_json_encode( $data ); | ||
| 129 | |||
| 130 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | ||
| 131 | echo "event: {$event}" . PHP_EOL; | ||
| 132 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | ||
| 133 | echo "data: {$data}" . PHP_EOL; | ||
| 134 | echo PHP_EOL; | ||
| 135 | |||
| 136 | $this->flush_buffers(); | ||
| 137 | } | ||
| 138 | |||
| 139 | /** | ||
| 140 | * Send an error to the client. | ||
| 141 | * | ||
| 142 | * @param array{message:string}|string $error Error message. | ||
| 143 | * | ||
| 144 | * @return void | ||
| 145 | */ | ||
| 146 | public function send_error( $error ) { | ||
| 147 | $this->send_event( 'error', $error ); | ||
| 148 | } | ||
| 149 | |||
| 150 | /** | ||
| 151 | * Check if the connection should abort. | ||
| 152 | * | ||
| 153 | * @return bool | ||
| 154 | */ | ||
| 155 | public function should_abort() { | ||
| 156 | return (bool) connection_aborted(); | ||
| 157 | } | ||
| 158 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Plugin controller. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Base; | ||
| 11 | |||
| 12 | defined( 'ABSPATH' ) || exit; | ||
| 13 | |||
| 14 | use Closure; | ||
| 15 | use stdClass; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Base Upgrade class. | ||
| 19 | */ | ||
| 20 | abstract class Upgrade implements \ContentControl\Interfaces\Upgrade { | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Type. | ||
| 24 | * | ||
| 25 | * @var string Uses data versioning types. | ||
| 26 | */ | ||
| 27 | const TYPE = ''; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Version. | ||
| 31 | * | ||
| 32 | * @var int | ||
| 33 | */ | ||
| 34 | const VERSION = 1; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Stream. | ||
| 38 | * | ||
| 39 | * @var \ContentControl\Services\UpgradeStream|null | ||
| 40 | */ | ||
| 41 | public $stream; | ||
| 42 | |||
| 43 | /** | ||
| 44 | * Upgrade constructor. | ||
| 45 | */ | ||
| 46 | public function __construct() { | ||
| 47 | } | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Upgrade label | ||
| 51 | * | ||
| 52 | * @return string | ||
| 53 | */ | ||
| 54 | abstract public function label(); | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Return full description for this upgrade. | ||
| 58 | * | ||
| 59 | * @return string | ||
| 60 | */ | ||
| 61 | public function description() { | ||
| 62 | return ''; | ||
| 63 | } | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Check if the upgrade is required. | ||
| 67 | * | ||
| 68 | * @return bool | ||
| 69 | */ | ||
| 70 | public function is_required() { | ||
| 71 | $current_version = \ContentControl\get_data_version( static::TYPE ); | ||
| 72 | return $current_version && $current_version < static::VERSION; | ||
| 73 | } | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Get the type of upgrade. | ||
| 77 | * | ||
| 78 | * @return string | ||
| 79 | */ | ||
| 80 | public function get_type() { | ||
| 81 | return static::TYPE; | ||
| 82 | } | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Check if the prerequisites are met. | ||
| 86 | * | ||
| 87 | * @return bool | ||
| 88 | */ | ||
| 89 | public function prerequisites_met() { | ||
| 90 | return true; | ||
| 91 | } | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Get the dependencies for this upgrade. | ||
| 95 | * | ||
| 96 | * @return string[] | ||
| 97 | */ | ||
| 98 | public function get_dependencies() { | ||
| 99 | return []; | ||
| 100 | } | ||
| 101 | |||
| 102 | /** | ||
| 103 | * Run the upgrade. | ||
| 104 | * | ||
| 105 | * @return void|\WP_Error|false | ||
| 106 | */ | ||
| 107 | abstract public function run(); | ||
| 108 | |||
| 109 | /** | ||
| 110 | * Run the upgrade. | ||
| 111 | * | ||
| 112 | * @param \ContentControl\Services\UpgradeStream $stream Stream. | ||
| 113 | * | ||
| 114 | * @return bool|\WP_Error | ||
| 115 | */ | ||
| 116 | public function stream_run( $stream ) { | ||
| 117 | $this->stream = $stream; | ||
| 118 | |||
| 119 | $return = $this->run(); | ||
| 120 | |||
| 121 | unset( $this->stream ); | ||
| 122 | |||
| 123 | if ( is_bool( $return ) || is_wp_error( $return ) ) { | ||
| 124 | return $return; | ||
| 125 | } | ||
| 126 | |||
| 127 | return true; | ||
| 128 | } | ||
| 129 | |||
| 130 | /** | ||
| 131 | * Return the stream. | ||
| 132 | * | ||
| 133 | * If no stream is available it returns a mock object with no-op methods to prevent errors. | ||
| 134 | * | ||
| 135 | * @return \ContentControl\Services\UpgradeStream|(object{ | ||
| 136 | * send_event: Closure, | ||
| 137 | * send_error: Closure, | ||
| 138 | * send_data: Closure, | ||
| 139 | * update_status: Closure, | ||
| 140 | * update_task_status: Closure, | ||
| 141 | * start_upgrades: Closure, | ||
| 142 | * complete_upgrades: Closure, | ||
| 143 | * start_task: Closure, | ||
| 144 | * update_task_progress:Closure, | ||
| 145 | * complete_task: Closure | ||
| 146 | * }&\stdClass) Stream. | ||
| 147 | */ | ||
| 148 | public function stream() { | ||
| 149 | $noop = | ||
| 150 | /** | ||
| 151 | * No-op. | ||
| 152 | * | ||
| 153 | * @return void | ||
| 154 | */ | ||
| 155 | function () {}; | ||
| 156 | |||
| 157 | return is_a( $this->stream, '\ContentControl\Services\UpgradeStream' ) ? $this->stream : (object) [ | ||
| 158 | 'send_event' => $noop, | ||
| 159 | 'send_error' => $noop, | ||
| 160 | 'send_data' => $noop, | ||
| 161 | 'update_status' => $noop, | ||
| 162 | 'update_task_status' => $noop, | ||
| 163 | 'start_upgrades' => $noop, | ||
| 164 | 'complete_upgrades' => $noop, | ||
| 165 | 'start_task' => $noop, | ||
| 166 | 'update_task_progress' => $noop, | ||
| 167 | 'complete_task' => $noop, | ||
| 168 | ]; | ||
| 169 | } | ||
| 170 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Admin controller. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2022, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Controllers; | ||
| 11 | |||
| 12 | use ContentControl\Base\Controller; | ||
| 13 | use ContentControl\Controllers\Admin\Reviews; | ||
| 14 | use ContentControl\Controllers\Admin\SettingsPage; | ||
| 15 | use ContentControl\Controllers\Admin\Upgrades; | ||
| 16 | use ContentControl\Controllers\Admin\UserExperience; | ||
| 17 | use ContentControl\Controllers\Admin\WidgetEditor; | ||
| 18 | |||
| 19 | defined( 'ABSPATH' ) || exit; | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Admin controller class. | ||
| 23 | * | ||
| 24 | * @package ContentControl | ||
| 25 | */ | ||
| 26 | class Admin extends Controller { | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Initialize admin controller. | ||
| 30 | * | ||
| 31 | * @return void | ||
| 32 | */ | ||
| 33 | public function init() { | ||
| 34 | $this->container->register_controllers( [ | ||
| 35 | 'Admin\Reviews' => new Reviews( $this->container ), | ||
| 36 | 'Admin\Settings' => new SettingsPage( $this->container ), | ||
| 37 | 'Admin\Upgrades' => new Upgrades( $this->container ), | ||
| 38 | 'Admin\UserExperience' => new UserExperience( $this->container ), | ||
| 39 | 'Admin\WidgetEditor' => new WidgetEditor( $this->container ), | ||
| 40 | ] ); | ||
| 41 | } | ||
| 42 | } |
This diff is collapsed.
Click to expand it.
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Settings Page Controller Class. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | */ | ||
| 7 | |||
| 8 | namespace ContentControl\Controllers\Admin; | ||
| 9 | |||
| 10 | use ContentControl\Base\Controller; | ||
| 11 | |||
| 12 | defined( 'ABSPATH' ) || exit; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Settings Page Controller. | ||
| 16 | * | ||
| 17 | * @package ContentControl\Admin | ||
| 18 | */ | ||
| 19 | class SettingsPage extends Controller { | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Initialize the settings page. | ||
| 23 | */ | ||
| 24 | public function init() { | ||
| 25 | add_action( 'admin_menu', [ $this, 'register_page' ], 999 ); | ||
| 26 | add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); | ||
| 27 | // These are here to listen for incoming webhooks from the Upgrader service when user chooses to install Pro. | ||
| 28 | add_action( 'wp_ajax_content_control_connect_verify_connection', [ $this, 'process_verify_connection' ] ); | ||
| 29 | add_action( 'wp_ajax_nopriv_content_control_connect_verify_connection', [ $this, 'process_verify_connection' ] ); | ||
| 30 | add_action( 'wp_ajax_content_control_connect_webhook', [ $this, 'process_webhook' ] ); | ||
| 31 | add_action( 'wp_ajax_nopriv_content_control_connect_webhook', [ $this, 'process_webhook' ] ); | ||
| 32 | } | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Register admin options pages. | ||
| 36 | * | ||
| 37 | * @return void | ||
| 38 | */ | ||
| 39 | public function register_page() { | ||
| 40 | add_options_page( | ||
| 41 | __( 'Content Control', 'content-control' ), | ||
| 42 | __( 'Content Control', 'content-control' ), | ||
| 43 | 'manage_options', | ||
| 44 | 'content-control-settings', | ||
| 45 | [ $this, 'render_page' ] | ||
| 46 | ); | ||
| 47 | } | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Render settings page title & container. | ||
| 51 | * | ||
| 52 | * @return void | ||
| 53 | */ | ||
| 54 | public function render_page() { | ||
| 55 | ?> | ||
| 56 | <div id="content-control-root-container"></div> | ||
| 57 | <script>jQuery(() => window.contentControl.settingsPage.init());</script> | ||
| 58 | <?php | ||
| 59 | } | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Enqueue assets for the settings page. | ||
| 63 | * | ||
| 64 | * @param string $hook Page hook name. | ||
| 65 | * | ||
| 66 | * @return void | ||
| 67 | */ | ||
| 68 | public function enqueue_scripts( $hook ) { | ||
| 69 | if ( 'settings_page_content-control-settings' !== $hook ) { | ||
| 70 | return; | ||
| 71 | } | ||
| 72 | |||
| 73 | wp_enqueue_editor(); | ||
| 74 | wp_tinymce_inline_scripts(); | ||
| 75 | |||
| 76 | wp_enqueue_script( 'content-control-settings-page' ); | ||
| 77 | } | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Verify the connection. | ||
| 81 | * | ||
| 82 | * @return void | ||
| 83 | */ | ||
| 84 | public function process_verify_connection() { | ||
| 85 | $this->container->get( 'connect' )->process_verify_connection(); | ||
| 86 | } | ||
| 87 | |||
| 88 | /** | ||
| 89 | * Listen for incoming secure webhooks from the API server. | ||
| 90 | * | ||
| 91 | * @return void | ||
| 92 | */ | ||
| 93 | public function process_webhook() { | ||
| 94 | $this->container->get( 'connect' )->process_webhook(); | ||
| 95 | } | ||
| 96 | } |
This diff is collapsed.
Click to expand it.
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Admin User Experience controller. | ||
| 4 | * | ||
| 5 | * @package ContentControl\Admin | ||
| 6 | * @copyright (c) 2023 Code Atlantic LLC | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Admin; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * UserExperience controller class. | ||
| 15 | */ | ||
| 16 | class UserExperience extends Controller { | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Initialize widget editor UX. | ||
| 20 | * | ||
| 21 | * @return void | ||
| 22 | */ | ||
| 23 | public function init() { | ||
| 24 | add_filter( 'plugin_action_links', [ $this, 'plugin_action_links' ], 10, 2 ); | ||
| 25 | add_action( "after_plugin_row_{$this->container->get('basename')}", [ | ||
| 26 | $this, | ||
| 27 | 'after_plugin_row', | ||
| 28 | ], 10, 2 ); | ||
| 29 | } | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Render plugin action links. | ||
| 33 | * | ||
| 34 | * @param array<string,string> $links Existing links. | ||
| 35 | * @param string $file Plugin file path. | ||
| 36 | * | ||
| 37 | * @return mixed | ||
| 38 | */ | ||
| 39 | public function plugin_action_links( $links, $file ) { | ||
| 40 | $basename = $this->container->get( 'basename' ); | ||
| 41 | |||
| 42 | if ( $file === $basename ) { | ||
| 43 | $plugin_action_links = apply_filters( | ||
| 44 | 'content_control/plugin_action_links', | ||
| 45 | [ | ||
| 46 | 'upgrade' => '<a target="_blank" href="https://contentcontrolplugin.com/pricing/?utm_campaign=upgrade-to-pro&utm_source=plugins-page&utm_medium=plugin-ui&utm_content=action-links-upgrade-text">' . __( 'Upgrade to Pro', 'content-control' ) . '</a>', | ||
| 47 | 'settings' => '<a href="' . admin_url( 'options-general.php?page=content-control-settings' ) . '">' . __( 'Settings', 'content-control' ) . '</a>', | ||
| 48 | ] | ||
| 49 | ); | ||
| 50 | |||
| 51 | if ( $this->container->is_license_active() || $this->container->is_pro_active() ) { | ||
| 52 | unset( $plugin_action_links['upgrade'] ); | ||
| 53 | } | ||
| 54 | |||
| 55 | if ( substr( get_locale(), 0, 2 ) === 'en' ) { | ||
| 56 | $plugin_action_links = array_merge( [ 'translate' => '<a href="' . sprintf( 'https://translate.wordpress.org/locale/%s/default/wp-plugins/content-control/', substr( get_locale(), 0, 2 ) ) . '" target="_blank">' . __( 'Translate', 'content-control' ) . '</a>' ], $plugin_action_links ); | ||
| 57 | } | ||
| 58 | |||
| 59 | foreach ( $plugin_action_links as $link ) { | ||
| 60 | array_unshift( $links, $link ); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | return $links; | ||
| 65 | } | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Add a row to the plugin list table. | ||
| 69 | * | ||
| 70 | * @param string $file Plugin file path. | ||
| 71 | * @param array<string,string|int> $plugin_data Plugin data. | ||
| 72 | * | ||
| 73 | * @return void | ||
| 74 | */ | ||
| 75 | public function after_plugin_row( $file, $plugin_data ) { | ||
| 76 | $plugin_slug = $this->container->get( 'slug' ); | ||
| 77 | |||
| 78 | if ( $plugin_slug !== $plugin_data['slug'] ) { | ||
| 79 | return; | ||
| 80 | } | ||
| 81 | |||
| 82 | $plugin_url = $this->container->get( 'url' ); | ||
| 83 | |||
| 84 | ?> | ||
| 85 | <!-- <tr class="plugin-update-tr active"> | ||
| 86 | <td colspan="3" class="plugin-update colspanchange"> | ||
| 87 | <div class="update-message notice inline notice-warning notice-alt"> | ||
| 88 | <p> | ||
| 89 | <?php | ||
| 90 | printf( | ||
| 91 | /* translators: %1$s: Plugin name, %2$s: Plugin URL */ | ||
| 92 | esc_html__( 'You are using the %1$s plugin. Please visit the %2$s to learn how to use it.', 'content-control' ), | ||
| 93 | '<strong>' . esc_html__( 'Content Control', 'content-control' ) . '</strong>', | ||
| 94 | '<a href="' . esc_url( $plugin_url ) . '" target="_blank">' . esc_html__( 'plugin website', 'content-control' ) . '</a>' | ||
| 95 | ); | ||
| 96 | ?> | ||
| 97 | </p> | ||
| 98 | </div> | ||
| 99 | </td> | ||
| 100 | </tr> --> | ||
| 101 | |||
| 102 | <?php | ||
| 103 | } | ||
| 104 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Admin Widget Editor controller. | ||
| 4 | * | ||
| 5 | * @note This is only used for the old WP -4.9 widget editor. | ||
| 6 | * | ||
| 7 | * @package ContentControl\Admin | ||
| 8 | * @copyright (c) 2023 Code Atlantic LLC | ||
| 9 | */ | ||
| 10 | |||
| 11 | namespace ContentControl\Controllers\Admin; | ||
| 12 | |||
| 13 | use WP_Widget; | ||
| 14 | use ContentControl\Base\Controller; | ||
| 15 | |||
| 16 | use function ContentControl\Rules\allowed_user_roles; | ||
| 17 | use function ContentControl\Widgets\parse_options as parse_widget_options; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * WidgetEditor controller class. | ||
| 21 | */ | ||
| 22 | class WidgetEditor extends Controller { | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Initialize widget editor UX. | ||
| 26 | * | ||
| 27 | * @return void | ||
| 28 | */ | ||
| 29 | public function init() { | ||
| 30 | add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); | ||
| 31 | add_action( 'in_widget_form', [ $this, 'fields' ], 5, 3 ); | ||
| 32 | add_filter( 'widget_update_callback', [ $this, 'save' ], 5, 3 ); | ||
| 33 | } | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Enqueue v1 admin scripts. | ||
| 37 | * | ||
| 38 | * @param mixed $hook Admin page hook name. | ||
| 39 | * | ||
| 40 | * @return void | ||
| 41 | */ | ||
| 42 | public function enqueue_assets( $hook ) { | ||
| 43 | if ( 'widgets.php' === $hook ) { | ||
| 44 | wp_enqueue_style( 'content-control-widget-editor' ); | ||
| 45 | wp_enqueue_script( 'content-control-widget-editor' ); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Renders additional widget option fields. | ||
| 51 | * | ||
| 52 | * @param \WP_Widget $widget Widget instance. | ||
| 53 | * @param bool $ret Whether to return the output. | ||
| 54 | * @param array<string,mixed> $instance Widget instance options. | ||
| 55 | * | ||
| 56 | * @return void | ||
| 57 | */ | ||
| 58 | public function fields( $widget, $ret, $instance ) { | ||
| 59 | $allowed_user_roles = allowed_user_roles(); | ||
| 60 | |||
| 61 | wp_nonce_field( 'content-control-widget-editor-nonce', 'content-control-widget-editor-nonce' ); | ||
| 62 | |||
| 63 | $which_users_options = [ | ||
| 64 | '' => __( 'Everyone', 'content-control' ), | ||
| 65 | 'logged_out' => __( 'Logged Out Users', 'content-control' ), | ||
| 66 | 'logged_in' => __( 'Logged In Users', 'content-control' ), | ||
| 67 | ]; | ||
| 68 | |||
| 69 | $instance = parse_widget_options( $instance ); | ||
| 70 | |||
| 71 | ?> | ||
| 72 | <p class="widget_options-which_users"> | ||
| 73 | <label for="<?php echo esc_attr( $widget->get_field_id( 'which_users' ) ); ?>"> | ||
| 74 | <?php esc_html_e( 'Who can see this widget?', 'content-control' ); ?><br /> | ||
| 75 | <select name="<?php echo esc_attr( $widget->get_field_name( 'which_users' ) ); ?>" id="<?php echo esc_attr( $widget->get_field_id( 'which_users' ) ); ?>" class="widefat"> | ||
| 76 | <?php foreach ( $which_users_options as $option => $label ) : ?> | ||
| 77 | <option value="<?php echo esc_attr( $option ); ?>" <?php selected( $option, $instance['which_users'] ); ?>> | ||
| 78 | <?php echo esc_html( $label ); ?> | ||
| 79 | </option> | ||
| 80 | <?php endforeach; ?> | ||
| 81 | </select> | ||
| 82 | </label> | ||
| 83 | </p> | ||
| 84 | |||
| 85 | <p class="widget_options-roles"> | ||
| 86 | <?php esc_html_e( 'Choose which roles can see this widget', 'content-control' ); ?><br /> | ||
| 87 | <?php foreach ( $allowed_user_roles as $option => $label ) : ?> | ||
| 88 | <label> | ||
| 89 | <input type="checkbox" name="<?php echo esc_attr( $widget->get_field_name( 'roles' ) ); ?>[]" value="<?php echo esc_attr( $option ); ?>" <?php checked( in_array( $option, $instance['roles'], true ), true ); ?>/> | ||
| 90 | <?php echo esc_html( $label ); ?> | ||
| 91 | </label> | ||
| 92 | <?php endforeach; ?> | ||
| 93 | </p> | ||
| 94 | <?php | ||
| 95 | } | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Validates & saves additional widget options. | ||
| 99 | * | ||
| 100 | * @param array<string,mixed> $instance Widget instance options. | ||
| 101 | * @param array<string,mixed> $new_instance New widget instance options. | ||
| 102 | * @param array<string,mixed> $old_instance Old widget instance options. | ||
| 103 | * | ||
| 104 | * @return array<string,mixed>|bool | ||
| 105 | */ | ||
| 106 | public function save( $instance, $new_instance, $old_instance ) { | ||
| 107 | if ( isset( $_POST['content-control-widget-editor-nonce'] ) && wp_verify_nonce( wp_unslash( sanitize_key( $_POST['content-control-widget-editor-nonce'] ) ), 'content-control-widget-editor-nonce' ) ) { | ||
| 108 | $new_instance = parse_widget_options( $new_instance ); | ||
| 109 | $instance['which_users'] = $new_instance['which_users']; | ||
| 110 | $instance['roles'] = $new_instance['roles']; | ||
| 111 | |||
| 112 | if ( 'logged_in' === $instance['which_users'] ) { | ||
| 113 | $allowed_roles = allowed_user_roles(); | ||
| 114 | |||
| 115 | // Validate chosen roles and remove non-allowed roles. | ||
| 116 | foreach ( (array) $instance['roles'] as $key => $role ) { | ||
| 117 | if ( ! array_key_exists( $role, $allowed_roles ) ) { | ||
| 118 | unset( $instance['roles'][ $key ] ); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | } else { | ||
| 122 | unset( $instance['roles'] ); | ||
| 123 | } | ||
| 124 | } else { | ||
| 125 | // Failed validation, use old instance. | ||
| 126 | $old_instance = parse_widget_options( $old_instance ); | ||
| 127 | $instance['which_users'] = $old_instance['which_users']; | ||
| 128 | |||
| 129 | if ( empty( $old_instance['roles'] ) ) { | ||
| 130 | unset( $instance['roles'] ); | ||
| 131 | } else { | ||
| 132 | $instance['roles'] = $old_instance['roles']; | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | return $instance; | ||
| 137 | } | ||
| 138 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Plugin assets controller. | ||
| 4 | * | ||
| 5 | * @package ContentControl\Admin | ||
| 6 | * @copyright (c) 2023 Code Atlantic LLC. | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | use function ContentControl\get_all_plugin_options; | ||
| 14 | use function ContentControl\Rules\allowed_user_roles; | ||
| 15 | |||
| 16 | defined( 'ABSPATH' ) || exit; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Admin assets controller. | ||
| 20 | * | ||
| 21 | * @package ContentControl\Admin | ||
| 22 | */ | ||
| 23 | class Assets extends Controller { | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Initialize the assets controller. | ||
| 27 | */ | ||
| 28 | public function init() { | ||
| 29 | add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ], 0 ); | ||
| 30 | add_action( 'admin_enqueue_scripts', [ $this, 'register_scripts' ], 0 ); | ||
| 31 | add_action( 'wp_print_scripts', [ $this, 'autoload_styles_for_scripts' ], 0 ); | ||
| 32 | add_action( 'admin_print_scripts', [ $this, 'autoload_styles_for_scripts' ], 0 ); | ||
| 33 | } | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Get list of plugin packages. | ||
| 37 | * | ||
| 38 | * @return array<string,array<string,mixed>> | ||
| 39 | */ | ||
| 40 | public function get_packages() { | ||
| 41 | $permissions = $this->container->get_permissions(); | ||
| 42 | |||
| 43 | foreach ( $permissions as $permission => $cap ) { | ||
| 44 | $permissions[ $permission ] = current_user_can( $cap ); | ||
| 45 | } | ||
| 46 | |||
| 47 | $wp_version = get_bloginfo( 'version' ); | ||
| 48 | // Strip last number from version as they won't be breaking changes. | ||
| 49 | $wp_version = preg_replace( '/\.\d+$/', '', $wp_version ); | ||
| 50 | |||
| 51 | $is_pro_installed = \ContentControl\is_plugin_installed( 'content-control-pro/content-control-pro.php' ); | ||
| 52 | |||
| 53 | $packages = [ | ||
| 54 | 'block-editor' => [ | ||
| 55 | 'handle' => 'content-control-block-editor', | ||
| 56 | 'styles' => true, | ||
| 57 | 'varsName' => 'contentControlBlockEditor', | ||
| 58 | 'vars' => [ | ||
| 59 | 'adminUrl' => admin_url(), | ||
| 60 | 'wpVersion' => $wp_version, | ||
| 61 | 'pluginUrl' => $this->container->get_url(), | ||
| 62 | 'advancedMode' => $this->container->get_option( 'advanced_mode', false ), | ||
| 63 | 'allowedBlocks' => [], | ||
| 64 | 'userRoles' => allowed_user_roles(), | ||
| 65 | 'excludedBlocks' => array_merge( $this->container->get_option( 'excludedBlocks', [] ), [ | ||
| 66 | 'core/nextpage', | ||
| 67 | 'core/freeform', | ||
| 68 | ] ), | ||
| 69 | 'permissions' => $permissions, | ||
| 70 | ], | ||
| 71 | ], | ||
| 72 | 'components' => [ | ||
| 73 | 'handle' => 'content-control-components', | ||
| 74 | 'styles' => true, | ||
| 75 | 'deps' => [ | ||
| 76 | // This is required for tinymce components. | ||
| 77 | 'wp-tinymce', | ||
| 78 | // This is required for all tinyMCE plugins. | ||
| 79 | 'wp-block-library', | ||
| 80 | ], | ||
| 81 | ], | ||
| 82 | 'core-data' => [ | ||
| 83 | 'handle' => 'content-control-core-data', | ||
| 84 | 'deps' => [ | ||
| 85 | 'wp-api', | ||
| 86 | ], | ||
| 87 | 'varsName' => 'contentControlCoreData', | ||
| 88 | 'vars' => [ | ||
| 89 | 'currentSettings' => get_all_plugin_options(), | ||
| 90 | ], | ||
| 91 | ], | ||
| 92 | 'data' => [ | ||
| 93 | 'handle' => 'content-control-data', | ||
| 94 | ], | ||
| 95 | 'fields' => [ | ||
| 96 | 'handle' => 'content-control-fields', | ||
| 97 | ], | ||
| 98 | 'icons' => [ | ||
| 99 | 'handle' => 'content-control-icons', | ||
| 100 | 'styles' => true, | ||
| 101 | ], | ||
| 102 | 'rule-engine' => [ | ||
| 103 | 'handle' => 'content-control-rule-engine', | ||
| 104 | 'varsName' => 'contentControlRuleEngine', | ||
| 105 | 'vars' => [ | ||
| 106 | 'adminUrl' => admin_url(), | ||
| 107 | 'registeredRules' => $this->container->get( 'rules' )->get_block_editor_rules(), | ||
| 108 | ], | ||
| 109 | 'styles' => true, | ||
| 110 | ], | ||
| 111 | 'settings-page' => [ | ||
| 112 | 'handle' => 'content-control-settings-page', | ||
| 113 | 'varsName' => 'contentControlSettingsPage', | ||
| 114 | 'vars' => [ | ||
| 115 | 'pluginUrl' => $this->container->get( 'url' ), | ||
| 116 | 'wpVersion' => $wp_version, | ||
| 117 | 'adminUrl' => admin_url(), | ||
| 118 | 'restBase' => 'content-control/v2', | ||
| 119 | 'userRoles' => allowed_user_roles(), | ||
| 120 | 'logUrl' => current_user_can( 'manage_options' ) ? $this->container->get( 'logging' )->get_file_url() : false, | ||
| 121 | 'rolesAndCaps' => wp_roles()->roles, | ||
| 122 | 'version' => $this->container->get( 'version' ), | ||
| 123 | 'permissions' => $permissions, | ||
| 124 | 'isProInstalled' => $is_pro_installed, | ||
| 125 | 'isProActivated' => $is_pro_installed && is_plugin_active( 'content-control-pro/content-control-pro.php' ), | ||
| 126 | ], | ||
| 127 | 'styles' => true, | ||
| 128 | ], | ||
| 129 | 'utils' => [ | ||
| 130 | 'handle' => 'content-control-utils', | ||
| 131 | ], | ||
| 132 | 'widget-editor' => [ | ||
| 133 | 'handle' => 'content-control-widget-editor', | ||
| 134 | 'styles' => true, | ||
| 135 | ], | ||
| 136 | ]; | ||
| 137 | |||
| 138 | return $packages; | ||
| 139 | } | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Register all package scripts & styles. | ||
| 143 | * | ||
| 144 | * @return void | ||
| 145 | */ | ||
| 146 | public function register_scripts() { | ||
| 147 | $packages = $this->get_packages(); | ||
| 148 | |||
| 149 | // Register front end block styles. | ||
| 150 | wp_register_style( 'content-control-block-styles', $this->container->get_url( 'dist/style-block-editor.css' ), [], $this->container->get( 'version' ) ); | ||
| 151 | |||
| 152 | foreach ( $packages as $package => $package_data ) { | ||
| 153 | $handle = $package_data['handle']; | ||
| 154 | $meta = $this->get_asset_meta( $package ); | ||
| 155 | |||
| 156 | $js_deps = isset( $package_data['deps'] ) ? $package_data['deps'] : []; | ||
| 157 | |||
| 158 | wp_register_script( $handle, $this->container->get_url( "dist/$package.js" ), array_merge( $meta['dependencies'], $js_deps ), $meta['version'], true ); | ||
| 159 | |||
| 160 | if ( isset( $package_data['styles'] ) && $package_data['styles'] ) { | ||
| 161 | wp_register_style( $handle, $this->container->get_url( "dist/$package.css" ), [ 'wp-components', 'wp-block-editor', 'dashicons' ], $meta['version'] ); | ||
| 162 | } | ||
| 163 | |||
| 164 | if ( isset( $package_data['varsName'] ) && ! empty( $package_data['vars'] ) ) { | ||
| 165 | $localized_vars = apply_filters( "content_control/{$package}_localized_vars", $package_data['vars'] ); | ||
| 166 | wp_localize_script( $handle, $package_data['varsName'], $localized_vars ); | ||
| 167 | } | ||
| 168 | |||
| 169 | /** | ||
| 170 | * May be extended to wp_set_script_translations( 'my-handle', 'my-domain', | ||
| 171 | * plugin_dir_path( MY_PLUGIN ) . 'languages' ) ). For details see | ||
| 172 | * https://make.wordpress.org/core/2018/11/09/new-javascript-i18n-support-in-wordpress/ | ||
| 173 | */ | ||
| 174 | wp_set_script_translations( $handle, 'content-control' ); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | /** | ||
| 179 | * Auto load styles if scripts are enqueued. | ||
| 180 | * | ||
| 181 | * @return void | ||
| 182 | */ | ||
| 183 | public function autoload_styles_for_scripts() { | ||
| 184 | $packages = $this->get_packages(); | ||
| 185 | |||
| 186 | foreach ( $packages as $package => $package_data ) { | ||
| 187 | if ( wp_script_is( $package_data['handle'], 'enqueued' ) ) { | ||
| 188 | if ( isset( $package_data['styles'] ) && $package_data['styles'] ) { | ||
| 189 | wp_enqueue_style( $package_data['handle'] ); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | /** | ||
| 196 | * Get asset meta from generated files. | ||
| 197 | * | ||
| 198 | * @param string $package Package name. | ||
| 199 | * @return array{dependencies:string[],version:string} | ||
| 200 | */ | ||
| 201 | public function get_asset_meta( $package ) { | ||
| 202 | $meta_path = $this->container->get_path( "dist/$package.asset.php" ); | ||
| 203 | return file_exists( $meta_path ) ? require $meta_path : [ | ||
| 204 | 'dependencies' => [], | ||
| 205 | 'version' => $this->container->get( 'version' ), | ||
| 206 | ]; | ||
| 207 | } | ||
| 208 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend general setup. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | defined( 'ABSPATH' ) || exit; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Class BlockEditor | ||
| 17 | * | ||
| 18 | * @version 2.0.0 | ||
| 19 | */ | ||
| 20 | class BlockEditor extends Controller { | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Initiate hooks & filter. | ||
| 24 | * | ||
| 25 | * @return void | ||
| 26 | */ | ||
| 27 | public function init() { | ||
| 28 | add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_assets' ] ); | ||
| 29 | add_action( 'enqueue_block_assets', [ $this, 'enqueue_block_assets' ] ); | ||
| 30 | } | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Enqueue block editor assets. | ||
| 34 | * | ||
| 35 | * @return void | ||
| 36 | */ | ||
| 37 | public function enqueue_assets() { | ||
| 38 | wp_enqueue_script( 'content-control-block-editor' ); | ||
| 39 | } | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Enqueue block assets. | ||
| 43 | * | ||
| 44 | * @return void | ||
| 45 | */ | ||
| 46 | public function enqueue_block_assets() { | ||
| 47 | wp_enqueue_style( 'content-control-block-styles' ); | ||
| 48 | } | ||
| 49 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Compatibility controller. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2022, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Controllers; | ||
| 11 | |||
| 12 | use ContentControl\Base\Controller; | ||
| 13 | use ContentControl\Controllers\Compatibility\BetterDocs; | ||
| 14 | use ContentControl\Controllers\Compatibility\Divi; | ||
| 15 | use ContentControl\Controllers\Compatibility\Elementor; | ||
| 16 | use ContentControl\Controllers\Compatibility\QueryMonitor; | ||
| 17 | use ContentControl\Controllers\Compatibility\TheEventsCalendar; | ||
| 18 | |||
| 19 | defined( 'ABSPATH' ) || exit; | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Admin controller class. | ||
| 23 | * | ||
| 24 | * @package ContentControl | ||
| 25 | */ | ||
| 26 | class Compatibility extends Controller { | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Initialize admin controller. | ||
| 30 | * | ||
| 31 | * @return void | ||
| 32 | */ | ||
| 33 | public function init() { | ||
| 34 | $this->container->register_controllers( [ | ||
| 35 | 'Compatibility\BetterDocs' => new BetterDocs( $this->container ), | ||
| 36 | 'Compatibility\Divi' => new Divi( $this->container ), | ||
| 37 | 'Compatibility\Elementor' => new Elementor( $this->container ), | ||
| 38 | 'Compatibility\QueryMonitor' => new QueryMonitor( $this->container ), | ||
| 39 | 'Compatibility\TheEventsCalendar' => new TheEventsCalendar( $this->container ), | ||
| 40 | ] ); | ||
| 41 | } | ||
| 42 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * BetterDocs controller class. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | */ | ||
| 7 | |||
| 8 | namespace ContentControl\Controllers\Compatibility; | ||
| 9 | |||
| 10 | use ContentControl\Base\Controller; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * BetterDocs controller class. | ||
| 14 | */ | ||
| 15 | class BetterDocs extends Controller { | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Initiate hooks & filter. | ||
| 19 | * | ||
| 20 | * @return void | ||
| 21 | */ | ||
| 22 | public function init() { | ||
| 23 | add_filter( 'content_control/get_rest_api_intent', [ $this, 'get_rest_api_intent' ], 10 ); | ||
| 24 | } | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Check if controller is enabled. | ||
| 28 | * | ||
| 29 | * @return bool | ||
| 30 | */ | ||
| 31 | public function controller_enabled() { | ||
| 32 | return defined( 'BETTERDOCS_PLUGIN_FILE' ); | ||
| 33 | } | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Get intent for BetterDocs. | ||
| 37 | * | ||
| 38 | * @param array<string,mixed> $intent Intent. | ||
| 39 | * | ||
| 40 | * @return array<string,mixed> | ||
| 41 | */ | ||
| 42 | public function get_rest_api_intent( $intent ) { | ||
| 43 | global $wp; | ||
| 44 | |||
| 45 | $rest_route = $wp->query_vars['rest_route']; | ||
| 46 | $endpoint_parts = explode( '/', str_replace( '/wp/v2/', '', $rest_route ) ); | ||
| 47 | |||
| 48 | // Set the custom search intent. | ||
| 49 | if ( isset( $wp->query_vars['search'] ) ) { | ||
| 50 | $intent['search'] = sanitize_title( $wp->query_vars['search'] ); | ||
| 51 | } | ||
| 52 | |||
| 53 | if ( 'unknown' === $intent['type'] && 'docs' === $intent['name'] ) { | ||
| 54 | // If we have a post type or taxonomy, the name is the first part (posts, categories). | ||
| 55 | $post_type = sanitize_key( $endpoint_parts[0] ); | ||
| 56 | |||
| 57 | if ( 'docs' === $post_type ) { | ||
| 58 | $intent['type'] = 'post_type'; | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | // phpcs:disable WordPress.Security.NonceVerification.Recommended | ||
| 63 | if ( isset( $_REQUEST['post_type'] ) ) { | ||
| 64 | $post_type = sanitize_text_field( wp_unslash( $_REQUEST['post_type'] ) ); | ||
| 65 | |||
| 66 | // Check if any ct_forced_* request aregs are set. If so we should use the post type intent. | ||
| 67 | if ( strpos( $post_type, 'ct_forced_' ) !== false ) { | ||
| 68 | $intent['type'] = 'post_type'; | ||
| 69 | |||
| 70 | $post_type = str_replace( 'ct_forced_', '', $post_type ); | ||
| 71 | |||
| 72 | $intent['name'] = explode( ':', $post_type ); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | // phpcs:enable WordPress.Security.NonceVerification.Recommended | ||
| 76 | |||
| 77 | return $intent; | ||
| 78 | } | ||
| 79 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Divi compatibility controller. | ||
| 4 | * | ||
| 5 | * @package ContentControl\Admin | ||
| 6 | * @copyright (c) 2023 Code Atlantic LLC | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Compatibility; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Divi controller class. | ||
| 15 | */ | ||
| 16 | class Divi extends Controller { | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Initialize widget editor UX. | ||
| 20 | * | ||
| 21 | * @return void | ||
| 22 | */ | ||
| 23 | public function init() { | ||
| 24 | add_filter( 'content_control/protection_is_disabled', [ $this, 'protection_is_disabled' ] ); | ||
| 25 | } | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Check if controller is enabled. | ||
| 29 | * | ||
| 30 | * @return bool | ||
| 31 | */ | ||
| 32 | public function controller_enabled() { | ||
| 33 | return defined( 'ET_CORE_VERSION' ); | ||
| 34 | } | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Conditionally disable Content Control for Divi builder. | ||
| 38 | * | ||
| 39 | * @param boolean $protection_is_disabled Whether protection is disabled. | ||
| 40 | * @return boolean | ||
| 41 | */ | ||
| 42 | public function protection_is_disabled( $protection_is_disabled ) { | ||
| 43 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended | ||
| 44 | if ( isset( $_GET['et_fb'] ) && ! empty( $_GET['et_fb'] ) ) { | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | |||
| 48 | return $protection_is_disabled; | ||
| 49 | } | ||
| 50 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Elementor compatibility controller. | ||
| 4 | * | ||
| 5 | * @package ContentControl\Admin | ||
| 6 | * @copyright (c) 2023 Code Atlantic LLC | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Compatibility; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Elementor controller class. | ||
| 15 | */ | ||
| 16 | class Elementor extends Controller { | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Initialize widget editor UX. | ||
| 20 | * | ||
| 21 | * @return void | ||
| 22 | */ | ||
| 23 | public function init() { | ||
| 24 | add_filter( 'content_control/post_types_to_ignore', [ $this, 'post_types_to_ignore' ] ); | ||
| 25 | add_filter( 'content_control/protection_is_disabled', [ $this, 'protection_is_disabled' ] ); | ||
| 26 | } | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Check if controller is enabled. | ||
| 30 | * | ||
| 31 | * @return bool | ||
| 32 | */ | ||
| 33 | public function controller_enabled() { | ||
| 34 | return class_exists( '\Elementor\Plugin' ) || did_action( 'elementor/loaded' ); | ||
| 35 | } | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Conditionally disable Content Control for Elementor builder. | ||
| 39 | * | ||
| 40 | * @param boolean $is_disabled Whether protection is disabled. | ||
| 41 | * @return boolean | ||
| 42 | */ | ||
| 43 | public function protection_is_disabled( $is_disabled ) { | ||
| 44 | // If already disabled, no reason to continue. | ||
| 45 | if ( $is_disabled || ! did_action( 'elementor/loaded' ) ) { | ||
| 46 | return $is_disabled; | ||
| 47 | } | ||
| 48 | |||
| 49 | return $this->elementor_builder_is_active(); | ||
| 50 | } | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Add Elementor font post type to ignored post types. | ||
| 54 | * | ||
| 55 | * @param string[] $post_types Post types to ignore. | ||
| 56 | * @return string[] | ||
| 57 | */ | ||
| 58 | public function post_types_to_ignore( $post_types ) { | ||
| 59 | $post_types[] = 'elementor_font'; | ||
| 60 | $post_types[] = 'elementor_icons'; | ||
| 61 | $post_types[] = 'elementor_library'; | ||
| 62 | $post_types[] = 'elementor_snippet'; | ||
| 63 | |||
| 64 | return $post_types; | ||
| 65 | } | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Check if Elementor builder is active. | ||
| 69 | * | ||
| 70 | * @return boolean | ||
| 71 | */ | ||
| 72 | public function elementor_builder_is_active() { | ||
| 73 | // Check if this is the admin theme builder app. | ||
| 74 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended Simple & direct string comparison. | ||
| 75 | if ( | ||
| 76 | is_admin() && | ||
| 77 | // Disable notices as this is a generic string comparison to prevent doing a lot of work. | ||
| 78 | // phpcs:disable WordPress.Security.NonceVerification.Recommended | ||
| 79 | ! empty( $_GET['page'] ) && | ||
| 80 | 'elementor-app' === $_GET['page'] | ||
| 81 | // phpcs:enable WordPress.Security.NonceVerification.Recommended | ||
| 82 | ) { | ||
| 83 | return true; | ||
| 84 | } | ||
| 85 | |||
| 86 | if ( ! class_exists( '\Elementor\Plugin' ) || ! isset( \Elementor\Plugin::$instance ) ) { | ||
| 87 | return false; | ||
| 88 | } | ||
| 89 | |||
| 90 | /** | ||
| 91 | * Elementor instance. | ||
| 92 | * | ||
| 93 | * @var \Elementor\Plugin $elementor | ||
| 94 | */ | ||
| 95 | $elementor = \Elementor\Plugin::$instance; | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Elementor preview instance. | ||
| 99 | * | ||
| 100 | * @var \Elementor\Preview|(object{is_preview_mod:\Closure}&\stdClass)|false $preview | ||
| 101 | */ | ||
| 102 | $preview = isset( $elementor->preview ) ? $elementor->preview : false; | ||
| 103 | |||
| 104 | if ( false === $preview || ! method_exists( $preview, 'is_preview_mode' ) ) { | ||
| 105 | return false; | ||
| 106 | } | ||
| 107 | |||
| 108 | // Check if the page builder is active. | ||
| 109 | return $preview->is_preview_mode(); | ||
| 110 | } | ||
| 111 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * QueryMonitor | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | */ | ||
| 7 | |||
| 8 | namespace ContentControl\Controllers\Compatibility; | ||
| 9 | |||
| 10 | use ContentControl\Base\Controller; | ||
| 11 | use ContentControl\QueryMonitor\Output; | ||
| 12 | use ContentControl\QueryMonitor\Collector; | ||
| 13 | use QM_Collectors; | ||
| 14 | |||
| 15 | use function ContentControl\is_frontend; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * QueryMonitor | ||
| 19 | */ | ||
| 20 | class QueryMonitor extends Controller { | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Initialize the class | ||
| 24 | * | ||
| 25 | * @return void | ||
| 26 | */ | ||
| 27 | public function init() { | ||
| 28 | $this->register_collector(); | ||
| 29 | add_filter( 'qm/outputter/html', [ $this, 'register_output_html' ], 10 ); | ||
| 30 | } | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Check if controller is enabled. | ||
| 34 | * | ||
| 35 | * @return bool | ||
| 36 | */ | ||
| 37 | public function controller_enabled() { | ||
| 38 | return class_exists( 'QueryMonitor' ); | ||
| 39 | } | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Register collector. | ||
| 43 | * | ||
| 44 | * @return void | ||
| 45 | */ | ||
| 46 | public function register_collector() { | ||
| 47 | QM_Collectors::add( new Collector() ); | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Add Query Monitor outputter. | ||
| 52 | * | ||
| 53 | * @param array<string,\QM_Output_Html> $output Outputters. | ||
| 54 | * @return array<string,\QM_Output_Html> Outputters. | ||
| 55 | */ | ||
| 56 | public function register_output_html( $output ) { | ||
| 57 | if ( ! is_frontend() ) { | ||
| 58 | return $output; | ||
| 59 | } | ||
| 60 | |||
| 61 | $collector = QM_Collectors::get( 'content-control' ); | ||
| 62 | |||
| 63 | if ( $collector ) { | ||
| 64 | $output['content-control'] = new Output( $collector ); | ||
| 65 | } | ||
| 66 | |||
| 67 | return $output; | ||
| 68 | } | ||
| 69 | } |
wp-content/plugins/content-control/classes/Controllers/Compatibility/TheEventsCalendar.php
0 → 100644
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * The Events Calendar | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | */ | ||
| 7 | |||
| 8 | namespace ContentControl\Controllers\Compatibility; | ||
| 9 | |||
| 10 | use ContentControl\Base\Controller; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * TheEventsCalendar controller class. | ||
| 14 | */ | ||
| 15 | class TheEventsCalendar extends Controller { | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Initiate hooks & filter. | ||
| 19 | * | ||
| 20 | * @return void | ||
| 21 | */ | ||
| 22 | public function init() { | ||
| 23 | // 'wp_redirect' | ||
| 24 | add_action( 'content_control/restrict_main_query', [ $this, 'restrict_main_query' ], 10 ); | ||
| 25 | } | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Check if controller is enabled. | ||
| 29 | * | ||
| 30 | * @return bool | ||
| 31 | */ | ||
| 32 | public function controller_enabled() { | ||
| 33 | return class_exists( '\Tribe__Events__Main' ) && defined( 'TRIBE_EVENTS_FILE' ); | ||
| 34 | } | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Handle restrictions on the main query. | ||
| 38 | * | ||
| 39 | * When the main query is set to be redirected, TEC was cancelling the redirect. Returing true will allow the redirect to happen. | ||
| 40 | * | ||
| 41 | * @return void | ||
| 42 | */ | ||
| 43 | public function restrict_main_query() { | ||
| 44 | // If during the main query, a redirect is called on the events page, we need to allow it to happen. | ||
| 45 | add_filter( 'wp_redirect', function ( $location ) { | ||
| 46 | // Only call this filter within the redirect filter. Limiting the scope of the filter. | ||
| 47 | add_filter( 'tec_events_views_v2_redirected', '__return_true' ); | ||
| 48 | return $location; | ||
| 49 | }, 0 ); | ||
| 50 | } | ||
| 51 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend general setup. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | use ContentControl\Controllers\Frontend\Blocks; | ||
| 14 | use ContentControl\Controllers\Frontend\Restrictions; | ||
| 15 | use ContentControl\Controllers\Frontend\Widgets; | ||
| 16 | |||
| 17 | defined( 'ABSPATH' ) || exit; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Class Frontend | ||
| 21 | */ | ||
| 22 | class Frontend extends Controller { | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Initialize Hooks & Filters | ||
| 26 | */ | ||
| 27 | public function init() { | ||
| 28 | $this->container->register_controllers([ | ||
| 29 | 'Frontend\Blocks' => new Blocks( $this->container ), | ||
| 30 | 'Frontend\Restrictions' => new Restrictions( $this->container ), | ||
| 31 | 'Frontend\Widgets' => new Widgets( $this->container ), | ||
| 32 | ]); | ||
| 33 | |||
| 34 | $this->hooks(); | ||
| 35 | } | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Register general frontend hooks. | ||
| 39 | * | ||
| 40 | * @return void | ||
| 41 | */ | ||
| 42 | public function hooks() { | ||
| 43 | $this->replicate_core_content_filters(); | ||
| 44 | |||
| 45 | add_filter( 'content_control/restricted_post_content', '\ContentControl\append_post_excerpts', 9, 2 ); | ||
| 46 | add_filter( 'content_control/restricted_post_excerpt', '\ContentControl\append_post_excerpts', 9, 2 ); | ||
| 47 | } | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Replicate core content filters. | ||
| 51 | * | ||
| 52 | * @return void | ||
| 53 | */ | ||
| 54 | private function replicate_core_content_filters() { | ||
| 55 | /** | ||
| 56 | * Instance of WP_Embed class. | ||
| 57 | * | ||
| 58 | * @var \WP_Embed $wp_embed | ||
| 59 | */ | ||
| 60 | global $wp_embed; | ||
| 61 | |||
| 62 | $the_content = 'content_control/restricted_post_content'; | ||
| 63 | $the_excerpt = 'content_control/restricted_post_excerpt'; | ||
| 64 | |||
| 65 | // These all follow WP core's `the_content` filter. | ||
| 66 | add_filter( $the_content, 'do_blocks', 9 ); | ||
| 67 | add_filter( $the_content, 'wptexturize' ); | ||
| 68 | add_filter( $the_content, 'convert_smilies', 20 ); | ||
| 69 | add_filter( $the_content, 'wpautop' ); | ||
| 70 | add_filter( $the_content, 'shortcode_unautop' ); | ||
| 71 | add_filter( $the_content, 'prepend_attachment' ); | ||
| 72 | add_filter( $the_content, 'wp_replace_insecure_home_url' ); | ||
| 73 | add_filter( $the_content, 'do_shortcode', 11 ); // AFTER wpautop(). | ||
| 74 | add_filter( $the_content, 'wp_filter_content_tags', 12 ); // Runs after do_shortcode(). | ||
| 75 | add_filter( $the_content, 'capital_P_dangit', 11 ); | ||
| 76 | add_filter( $the_content, [ $wp_embed, 'run_shortcode' ], 8 ); | ||
| 77 | add_filter( $the_content, [ $wp_embed, 'autoembed' ], 8 ); | ||
| 78 | |||
| 79 | // These all follow WP core's `the_excerpt` filter. | ||
| 80 | add_filter( $the_excerpt, 'wptexturize' ); | ||
| 81 | add_filter( $the_excerpt, 'convert_smilies' ); | ||
| 82 | add_filter( $the_excerpt, 'convert_chars' ); | ||
| 83 | add_filter( $the_excerpt, 'wpautop' ); | ||
| 84 | add_filter( $the_excerpt, 'shortcode_unautop' ); | ||
| 85 | add_filter( $the_excerpt, 'wp_replace_insecure_home_url' ); | ||
| 86 | add_filter( $the_excerpt, 'wp_filter_content_tags', 12 ); | ||
| 87 | add_filter( $the_excerpt, 'capital_P_dangit', 11 ); | ||
| 88 | } | ||
| 89 | } |
This diff is collapsed.
Click to expand it.
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend restrictions setup. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Frontend; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | use ContentControl\Controllers\Frontend\Restrictions\MainQuery; | ||
| 13 | use ContentControl\Controllers\Frontend\Restrictions\QueryPosts; | ||
| 14 | use ContentControl\Controllers\Frontend\Restrictions\PostContent; | ||
| 15 | use ContentControl\Controllers\Frontend\Restrictions\QueryTerms; | ||
| 16 | use ContentControl\Controllers\Frontend\Restrictions\RestAPI; | ||
| 17 | |||
| 18 | defined( 'ABSPATH' ) || exit; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Class for handling global restrictions. | ||
| 22 | * | ||
| 23 | * @package ContentControl | ||
| 24 | */ | ||
| 25 | class Restrictions extends Controller { | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Initiate functionality. | ||
| 29 | */ | ||
| 30 | public function init() { | ||
| 31 | $this->container->register_controllers( [ | ||
| 32 | 'Frontend\Restrictions\MainQuery' => new MainQuery( $this->container ), | ||
| 33 | 'Frontend\Restrictions\QueryPosts' => new QueryPosts( $this->container ), | ||
| 34 | 'Frontend\Restrictions\QueryTerms' => new QueryTerms( $this->container ), | ||
| 35 | 'Frontend\Restrictions\PostContent' => new PostContent( $this->container ), | ||
| 36 | 'Frontend\Restrictions\RestAPI' => new RestAPI( $this->container ), | ||
| 37 | ] ); | ||
| 38 | } | ||
| 39 | } |
wp-content/plugins/content-control/classes/Controllers/Frontend/Restrictions/MainQuery.php
0 → 100644
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend main query restrictions. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Frontend\Restrictions; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | use function ContentControl\redirect; | ||
| 14 | use function ContentControl\get_main_wp_query; | ||
| 15 | use function ContentControl\set_query_to_page; | ||
| 16 | use function ContentControl\reset_query_context; | ||
| 17 | use function ContentControl\query_can_be_ignored; | ||
| 18 | use function ContentControl\content_is_restricted; | ||
| 19 | use function ContentControl\override_query_context; | ||
| 20 | use function ContentControl\protection_is_disabled; | ||
| 21 | use function ContentControl\get_applicable_restriction; | ||
| 22 | use function ContentControl\get_restriction_matches_for_queried_posts; | ||
| 23 | |||
| 24 | defined( 'ABSPATH' ) || exit; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Class for handling global restrictions of the Main Query. | ||
| 28 | * | ||
| 29 | * @package ContentControl | ||
| 30 | */ | ||
| 31 | class MainQuery extends Controller { | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Initiate functionality. | ||
| 35 | */ | ||
| 36 | public function init() { | ||
| 37 | // This can be done no later than template_redirect, and no sooner than send_headers (when conditional tags are available). | ||
| 38 | // Can be done on send_headers, posts_selection, or wp as well. | ||
| 39 | add_action( 'template_redirect', [ $this, 'restrict_main_query' ], 10 ); | ||
| 40 | } | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Handle a restriction on the main query. | ||
| 44 | * | ||
| 45 | * NOTE: This is only for redirecting or replacing pages and | ||
| 46 | * should not be used to filter or hide post contents. | ||
| 47 | * | ||
| 48 | * @return void | ||
| 49 | */ | ||
| 50 | public function restrict_main_query() { | ||
| 51 | if ( ! \is_main_query() || protection_is_disabled() ) { | ||
| 52 | return; | ||
| 53 | } | ||
| 54 | |||
| 55 | $this->check_main_query(); | ||
| 56 | $this->check_main_query_posts(); | ||
| 57 | } | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Handle restrictions on the main query. | ||
| 61 | * | ||
| 62 | * NOTE: This is only for redirecting or replacing archives and | ||
| 63 | * should not be used to filter or hide post contents. | ||
| 64 | * | ||
| 65 | * @return void | ||
| 66 | */ | ||
| 67 | public function check_main_query() { | ||
| 68 | // Bail if we didn't match any restrictions. | ||
| 69 | if ( content_is_restricted() ) { | ||
| 70 | $restriction = get_applicable_restriction(); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Use this filter to prevent a post from being restricted, or to handle it yourself. | ||
| 74 | * | ||
| 75 | * @param null|mixed $pre Whether to prevent the post from being restricted. | ||
| 76 | * @param null|\ContentControl\Models\Restriction $restriction Restriction object. | ||
| 77 | * @return null|mixed | ||
| 78 | */ | ||
| 79 | if ( null !== apply_filters( 'content_control/pre_restrict_main_query', null, $restriction ) ) { | ||
| 80 | return; | ||
| 81 | } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Fires when a post is restricted, but before the restriction is handled. | ||
| 85 | * | ||
| 86 | * @param \ContentControl\Models\Restriction $restriction Restriction object. | ||
| 87 | */ | ||
| 88 | do_action( 'content_control/restrict_main_query', $restriction ); | ||
| 89 | |||
| 90 | $method = $restriction->get_setting( 'protectionMethod' ); | ||
| 91 | |||
| 92 | switch ( $method ) { | ||
| 93 | case 'redirect': | ||
| 94 | redirect( $restriction->get_setting( 'redirectType' ), $restriction->get_setting( 'redirectUrl' ) ); | ||
| 95 | return; | ||
| 96 | |||
| 97 | case 'replace': | ||
| 98 | if ( 'page' === $restriction->get_setting( 'replacementType' ) ) { | ||
| 99 | set_query_to_page( $restriction->get_setting( 'replacementPage' ) ); | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Handle restrictions on the main query posts. | ||
| 108 | * | ||
| 109 | * NOTE: This is only for redirecting or replacing archives and | ||
| 110 | * should not be used to filter or hide post contents. | ||
| 111 | * | ||
| 112 | * @return void | ||
| 113 | */ | ||
| 114 | public function check_main_query_posts() { | ||
| 115 | $query = get_main_wp_query(); | ||
| 116 | |||
| 117 | if ( query_can_be_ignored( $query ) ) { | ||
| 118 | return; | ||
| 119 | } | ||
| 120 | |||
| 121 | // Ensure rules are checked in the correct context. | ||
| 122 | override_query_context( 'main/posts' ); | ||
| 123 | |||
| 124 | // Get restrictions for the queried posts. | ||
| 125 | $post_restrictions = get_restriction_matches_for_queried_posts( $query ); | ||
| 126 | |||
| 127 | // Reset query context. | ||
| 128 | reset_query_context(); | ||
| 129 | |||
| 130 | if ( ! $post_restrictions ) { | ||
| 131 | return; | ||
| 132 | } | ||
| 133 | |||
| 134 | // Only the highest priority restriction is needed with redirect or replace handling. | ||
| 135 | $restriction_match = false; | ||
| 136 | |||
| 137 | // Find the highest priority restriction with redirect or replace handling. | ||
| 138 | foreach ( $post_restrictions as $post_restriction ) { | ||
| 139 | /** | ||
| 140 | * Restriction object. | ||
| 141 | * | ||
| 142 | * @var \ContentControl\Models\Restriction | ||
| 143 | */ | ||
| 144 | $restriction = $post_restriction['restriction']; | ||
| 145 | |||
| 146 | if ( 'redirect' === $restriction->get_setting( 'archiveHandling' ) || 'replace_archive_page' === $restriction->get_setting( 'archiveHandling' ) ) { | ||
| 147 | $restriction_match = $post_restriction; | ||
| 148 | break; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | if ( false === $restriction_match ) { | ||
| 153 | return; | ||
| 154 | } | ||
| 155 | |||
| 156 | /** | ||
| 157 | * Restriction object. | ||
| 158 | * | ||
| 159 | * @var \ContentControl\Models\Restriction | ||
| 160 | */ | ||
| 161 | $restriction = $restriction_match['restriction']; | ||
| 162 | $post_ids = $restriction_match['post_ids']; | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Use this filter to prevent a post from being restricted, or to handle it yourself. | ||
| 166 | * | ||
| 167 | * @param null|mixed $pre Whether to prevent the post from being restricted. | ||
| 168 | * @param null|\ContentControl\Models\Restriction $restriction Restriction object. | ||
| 169 | * @param int[] $post_id Post ID. | ||
| 170 | * | ||
| 171 | * @return null|mixed | ||
| 172 | */ | ||
| 173 | if ( null !== apply_filters( 'content_control/pre_restrict_main_query_post', null, $restriction, $post_ids ) ) { | ||
| 174 | return; | ||
| 175 | } | ||
| 176 | |||
| 177 | /** | ||
| 178 | * Fires when a post is restricted, but before the restriction is handled. | ||
| 179 | * | ||
| 180 | * @param \ContentControl\Models\Restriction $restriction Restriction object. | ||
| 181 | * @param int[] $post_id Post ID. | ||
| 182 | */ | ||
| 183 | do_action( 'content_control/restrict_main_query_post', $restriction, $post_ids ); | ||
| 184 | |||
| 185 | switch ( $restriction->get_setting( 'archiveHandling' ) ) { | ||
| 186 | case 'replace_archive_page': | ||
| 187 | set_query_to_page( $restriction->get_setting( 'archiveReplacementPage' ) ); | ||
| 188 | break; | ||
| 189 | case 'redirect': | ||
| 190 | redirect( $restriction->get_setting( 'archiveRedirectType' ), $restriction->get_setting( 'archiveRedirectUrl' ) ); | ||
| 191 | break; | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } |
wp-content/plugins/content-control/classes/Controllers/Frontend/Restrictions/PostContent.php
0 → 100644
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend post content restrictions. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Frontend\Restrictions; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | use function ContentControl\content_is_restricted; | ||
| 14 | use function ContentControl\protection_is_disabled; | ||
| 15 | use function ContentControl\get_applicable_restriction; | ||
| 16 | |||
| 17 | defined( 'ABSPATH' ) || exit; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Class for handling global restrictions of the post contents. | ||
| 21 | * | ||
| 22 | * @package ContentControl | ||
| 23 | */ | ||
| 24 | class PostContent extends Controller { | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Initiate functionality. | ||
| 28 | */ | ||
| 29 | public function init() { | ||
| 30 | $this->enable_filters(); | ||
| 31 | } | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Enable filters. | ||
| 35 | * | ||
| 36 | * @return void | ||
| 37 | */ | ||
| 38 | public function enable_filters() { | ||
| 39 | add_filter( 'the_content', [ $this, 'filter_the_content_if_restricted' ], 1000 ); | ||
| 40 | add_filter( 'get_the_excerpt', [ $this, 'filter_the_excerpt_if_restricted' ], 1000, 2 ); | ||
| 41 | // phpcs:disable Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.InlineComment.InvalidEndChar -- These are for future use. | ||
| 42 | // add_filter( 'the_title', [ $this, 'filter_the_title_if_restricted'], 1000, 2 ); | ||
| 43 | // add_filter( 'get_the_excerpt', [ $this, 'filter_the_excerpt_if_restricted' ], 1000, 2 ); | ||
| 44 | // add_filter( 'post_class', [ $this, 'filter_post_class_if_restricted' ], 1000, 3 ); | ||
| 45 | // add_filter( 'post_password_required', [ $this, 'require_password_if_restricted' ], 1000, 2 ); | ||
| 46 | // add_filter( 'the_password_form', [ $this, 'filter_password_form_if_restricted' ], 1000, 2 ); | ||
| 47 | // phpcs:enable Squiz.PHP.CommentedOutCode.Found, Squiz.Commenting.InlineComment.InvalidEndChar | ||
| 48 | } | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Disable filters. | ||
| 52 | * | ||
| 53 | * @return void | ||
| 54 | */ | ||
| 55 | public function disable_filters() { | ||
| 56 | remove_filter( 'the_content', [ $this, 'filter_the_content_if_restricted' ], 1000 ); | ||
| 57 | remove_filter( 'get_the_excerpt', [ $this, 'filter_the_excerpt_if_restricted' ], 1000 ); | ||
| 58 | } | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Filter post content when needed. | ||
| 62 | * | ||
| 63 | * NOTE: If we got this far with restricted content, this is the last attempt to protect | ||
| 64 | * it. This serves as the default fallback protection method if all others fail. | ||
| 65 | * | ||
| 66 | * @param string $content Content of post being checked. | ||
| 67 | * | ||
| 68 | * @return string | ||
| 69 | */ | ||
| 70 | public function filter_the_content_if_restricted( $content ) { | ||
| 71 | $filter_name = 'content_control/restricted_post_content'; | ||
| 72 | |||
| 73 | // If this isn't a post type that can be restricted, bail. | ||
| 74 | if ( protection_is_disabled() ) { | ||
| 75 | return $content; | ||
| 76 | } | ||
| 77 | |||
| 78 | if ( ! content_is_restricted() ) { | ||
| 79 | return $content; | ||
| 80 | } | ||
| 81 | |||
| 82 | // Ensure we don't get into an infinite loop. | ||
| 83 | if ( doing_filter( $filter_name ) || doing_filter( 'get_the_excerpt' ) ) { | ||
| 84 | return $content; | ||
| 85 | } | ||
| 86 | |||
| 87 | $restriction = get_applicable_restriction(); | ||
| 88 | |||
| 89 | if ( false === $restriction ) { | ||
| 90 | return $content; | ||
| 91 | } | ||
| 92 | |||
| 93 | // If this is a replacement page, bail. | ||
| 94 | if ( | ||
| 95 | ( 'replace' === $restriction->get_setting( 'protectionMethod' ) && 'page' === $restriction->get_setting( 'replacementType' ) && is_page( $restriction->get_setting( 'replacementPage' ) ) ) || | ||
| 96 | ( 'replace_archive_page' === $restriction->get_setting( 'archiveHandling' ) && is_page( $restriction->get_setting( 'archiveReplacementPage' ) ) ) | ||
| 97 | ) { | ||
| 98 | return $content; | ||
| 99 | } | ||
| 100 | |||
| 101 | $message = \ContentControl\get_default_denial_message(); | ||
| 102 | |||
| 103 | /** | ||
| 104 | * If the restriction has a custom message, use it. | ||
| 105 | * | ||
| 106 | * We could check $restriction->replacement_type, but we need a safe default for | ||
| 107 | * all cases. Further we do content filtering for all sub queries and currently | ||
| 108 | * don't offer a way to override the message for those. | ||
| 109 | * | ||
| 110 | * In this way currently users can change to content replacement, set the override | ||
| 111 | * message, then change back to page replacement and the override message will still | ||
| 112 | * be used for the post in sub queries. | ||
| 113 | */ | ||
| 114 | if ( $restriction->get_setting( 'overrideMessage' ) ) { | ||
| 115 | $message = $restriction->get_message(); | ||
| 116 | } | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Filter the message to display when a post is restricted. | ||
| 120 | * | ||
| 121 | * @param string $message Message to display. | ||
| 122 | * @param object $restriction Restriction object. | ||
| 123 | * | ||
| 124 | * @return string | ||
| 125 | */ | ||
| 126 | return apply_filters( | ||
| 127 | $filter_name, | ||
| 128 | // If the default message is empty, show a generic message. | ||
| 129 | ! empty( $message ) ? $message : __( 'This content is restricted.', 'content-control' ), | ||
| 130 | $restriction | ||
| 131 | ); | ||
| 132 | } | ||
| 133 | |||
| 134 | /** | ||
| 135 | * Filter post excerpt when needed. | ||
| 136 | * | ||
| 137 | * @param string $post_excerpt The post excerpt. | ||
| 138 | * @param \WP_Post $post Post object. | ||
| 139 | * | ||
| 140 | * @return string | ||
| 141 | */ | ||
| 142 | public function filter_the_excerpt_if_restricted( $post_excerpt, $post = null ) { | ||
| 143 | $filter_name = 'content_control/restricted_post_excerpt'; | ||
| 144 | |||
| 145 | // If this isn't a post type that can be restricted, bail. | ||
| 146 | if ( protection_is_disabled() ) { | ||
| 147 | return $post_excerpt; | ||
| 148 | } | ||
| 149 | |||
| 150 | if ( ! content_is_restricted( $post->ID ) ) { | ||
| 151 | return $post_excerpt; | ||
| 152 | } | ||
| 153 | |||
| 154 | if ( doing_filter( $filter_name ) ) { | ||
| 155 | return $post_excerpt; | ||
| 156 | } | ||
| 157 | |||
| 158 | $restriction = get_applicable_restriction(); | ||
| 159 | |||
| 160 | /** | ||
| 161 | * Filter the excerpt to display when a post is restricted. | ||
| 162 | * | ||
| 163 | * @param string $message Message to display. | ||
| 164 | * @param object $restriction Restriction object. | ||
| 165 | * | ||
| 166 | * @return string | ||
| 167 | */ | ||
| 168 | return apply_filters( | ||
| 169 | $filter_name, | ||
| 170 | $restriction->get_message(), | ||
| 171 | $restriction | ||
| 172 | ); | ||
| 173 | } | ||
| 174 | } |
wp-content/plugins/content-control/classes/Controllers/Frontend/Restrictions/QueryPosts.php
0 → 100644
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend query post restrictions. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Frontend\Restrictions; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | use function ContentControl\query_can_be_ignored; | ||
| 14 | use function ContentControl\protection_is_disabled; | ||
| 15 | use function ContentControl\get_restriction_matches_for_queried_posts; | ||
| 16 | |||
| 17 | defined( 'ABSPATH' ) || exit; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Class for handling global restrictions of the query posts. | ||
| 21 | * | ||
| 22 | * @package ContentControl | ||
| 23 | */ | ||
| 24 | class QueryPosts extends Controller { | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Initiate functionality. | ||
| 28 | * | ||
| 29 | * @return void | ||
| 30 | */ | ||
| 31 | public function init() { | ||
| 32 | // We delay this until functions.php is loaded, so that users can use the content_control/query_filter_init_hook filter. | ||
| 33 | // The assumption is that most code should be registered by init 999999, so we'll use that as the default. | ||
| 34 | add_action( 'init', [ $this, 'register_hooks' ], 999999 ); | ||
| 35 | } | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Register hooks. | ||
| 39 | * | ||
| 40 | * @return void | ||
| 41 | */ | ||
| 42 | public function register_hooks() { | ||
| 43 | /** | ||
| 44 | * Use this filter to change the hook used to add query post filtering. | ||
| 45 | * | ||
| 46 | * This only applies to alternate queries, not the main query, and is used for removing | ||
| 47 | * posts from the query that are restricted. | ||
| 48 | * | ||
| 49 | * - Register earlier for more restriction coverage. | ||
| 50 | * - Register later for more compatibility with other plugins that late register post types. | ||
| 51 | * | ||
| 52 | * @param null|string $init_hook The hook to use to add the query post filtering. | ||
| 53 | * @return null|string The hook to use, should be: wp_loaded, or maybe even parse_query or wp (if you know what you're doing). | ||
| 54 | */ | ||
| 55 | $init_hook = apply_filters( 'content_control/query_filter_init_hook', null ); | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Use this filter to change the priority used to add query post filtering. | ||
| 59 | * | ||
| 60 | * @param int $init_priority The priority to use to add the query post filtering. | ||
| 61 | * @return int The priority to use. Default: 999. | ||
| 62 | */ | ||
| 63 | $init_priority = apply_filters( 'content_control/query_filter_init_priority', 999 ); | ||
| 64 | |||
| 65 | if ( is_null( $init_hook ) || ! did_action( $init_hook ) ) { | ||
| 66 | // If the user has not specified a hook, we'll use the default (now). | ||
| 67 | $this->enable_query_filtering(); | ||
| 68 | return; | ||
| 69 | } | ||
| 70 | |||
| 71 | add_action( (string) $init_hook, [ $this, 'enable_query_filtering' ], (int) $init_priority ); | ||
| 72 | } | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Late hooks. | ||
| 76 | * | ||
| 77 | * @return void | ||
| 78 | */ | ||
| 79 | public function enable_query_filtering() { | ||
| 80 | add_filter( 'the_posts', [ $this, 'restrict_query_posts' ], 10, 2 ); | ||
| 81 | } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Handle restricted content appropriately. | ||
| 85 | * | ||
| 86 | * NOTE. This is only for filtering posts, and should not | ||
| 87 | * be used to redirect or replace the entire page. | ||
| 88 | * | ||
| 89 | * @param \WP_Post[] $posts Array of post objects. | ||
| 90 | * @param \WP_Query $query The WP_Query instance (passed by reference). | ||
| 91 | * | ||
| 92 | * @return \WP_Post[] | ||
| 93 | */ | ||
| 94 | public function restrict_query_posts( $posts, $query ) { | ||
| 95 | if ( query_can_be_ignored( $query ) ) { | ||
| 96 | return $posts; | ||
| 97 | } | ||
| 98 | |||
| 99 | if ( protection_is_disabled() ) { | ||
| 100 | return $posts; | ||
| 101 | } | ||
| 102 | |||
| 103 | $post_restrictions = get_restriction_matches_for_queried_posts( $query ); | ||
| 104 | |||
| 105 | if ( false === $post_restrictions ) { | ||
| 106 | return $posts; | ||
| 107 | } | ||
| 108 | |||
| 109 | // If we have restrictions on the queried posts, handle them top down. | ||
| 110 | foreach ( $post_restrictions as $match ) { | ||
| 111 | $post_id = $match['post_ids']; | ||
| 112 | $restriction = $match['restriction']; | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Use this filter to prevent a post from being restricted, or to handle it yourself. | ||
| 116 | * | ||
| 117 | * @param null|mixed $pre Whether to prevent the post from being restricted. | ||
| 118 | * @param null|\ContentControl\Models\Restriction $restriction Restriction object. | ||
| 119 | * @param int[] $post_id Post ID. | ||
| 120 | * @return null|mixed | ||
| 121 | */ | ||
| 122 | if ( null !== apply_filters( 'content_control/pre_restrict_archive_post', null, $restriction, $post_id ) ) { | ||
| 123 | continue; | ||
| 124 | } | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Fires when a post is restricted, but before the restriction is handled. | ||
| 128 | * | ||
| 129 | * @param \ContentControl\Models\Restriction $restriction Restriction object. | ||
| 130 | * @param int[] $post_id Post ID. | ||
| 131 | */ | ||
| 132 | do_action( 'content_control/restrict_archive_post', $restriction, $post_id ); | ||
| 133 | |||
| 134 | $handling = $query->is_main_query() ? $restriction->get_setting( 'archiveHandling' ) : $restriction->get_setting( 'additionalQueryHandling' ); | ||
| 135 | |||
| 136 | switch ( $handling ) { | ||
| 137 | case 'filter_post_content': | ||
| 138 | // Filter the title/excerpt/contents of the restricted items. | ||
| 139 | break; | ||
| 140 | case 'hide': | ||
| 141 | foreach ( $posts as $key => $post ) { | ||
| 142 | if ( in_array( $post->ID, $post_id, true ) ) { | ||
| 143 | unset( $posts[ $key ] ); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | // Update the query's post count. | ||
| 148 | $query->post_count = count( $posts ); | ||
| 149 | // Reset post indexes. | ||
| 150 | $posts = array_values( $posts ); | ||
| 151 | break; | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | return $posts; | ||
| 156 | } | ||
| 157 | } |
wp-content/plugins/content-control/classes/Controllers/Frontend/Restrictions/QueryTerms.php
0 → 100644
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend query post restrictions. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Frontend\Restrictions; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | use function ContentControl\query_can_be_ignored; | ||
| 14 | use function ContentControl\protection_is_disabled; | ||
| 15 | use function ContentControl\get_restriction_matches_for_queried_terms; | ||
| 16 | use function ContentControl\is_rest; | ||
| 17 | |||
| 18 | defined( 'ABSPATH' ) || exit; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Class for handling global restrictions of the query posts. | ||
| 22 | * | ||
| 23 | * @package ContentControl | ||
| 24 | */ | ||
| 25 | class QueryTerms extends Controller { | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Initiate functionality. | ||
| 29 | * | ||
| 30 | * @return void | ||
| 31 | */ | ||
| 32 | public function init() { | ||
| 33 | // We delay this until functions.php is loaded, so that users can use the content_control/query_filter_init_hook filter. | ||
| 34 | // The assumption is that most code should be registered by init 999999, so we'll use that as the default. | ||
| 35 | add_action( 'init', [ $this, 'register_hooks' ], 999999 ); | ||
| 36 | } | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Register hooks. | ||
| 40 | * | ||
| 41 | * @return void | ||
| 42 | */ | ||
| 43 | public function register_hooks() { | ||
| 44 | /** | ||
| 45 | * Use this filter to change the hook used to add query post filtering. | ||
| 46 | * | ||
| 47 | * This only applies to alternate queries, not the main query, and is used for removing | ||
| 48 | * posts from the query that are restricted. | ||
| 49 | * | ||
| 50 | * - Register earlier for more restriction coverage. | ||
| 51 | * - Register later for more compatibility with other plugins that late register post types. | ||
| 52 | * | ||
| 53 | * @param null|string $init_hook The hook to use to add the query post filtering. | ||
| 54 | * @return null|string The hook to use, should be: wp_loaded, or maybe even parse_query or wp (if you know what you're doing). | ||
| 55 | */ | ||
| 56 | $init_hook = apply_filters( 'content_control/query_filter_init_hook', null ); | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Use this filter to change the priority used to add query post filtering. | ||
| 60 | * | ||
| 61 | * @param int $init_priority The priority to use to add the query post filtering. | ||
| 62 | * @return int The priority to use. Default: 999. | ||
| 63 | */ | ||
| 64 | $init_priority = apply_filters( 'content_control/query_filter_init_priority', 999 ); | ||
| 65 | |||
| 66 | if ( is_null( $init_hook ) || ! did_action( $init_hook ) ) { | ||
| 67 | // If the user has not specified a hook, we'll use the default (now). | ||
| 68 | $this->enable_query_filtering(); | ||
| 69 | return; | ||
| 70 | } | ||
| 71 | |||
| 72 | add_action( (string) $init_hook, [ $this, 'enable_query_filtering' ], (int) $init_priority ); | ||
| 73 | } | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Late hooks. | ||
| 77 | * | ||
| 78 | * @return void | ||
| 79 | */ | ||
| 80 | public function enable_query_filtering() { | ||
| 81 | add_filter( 'get_terms', [ $this, 'restrict_query_terms' ], 10, 4 ); | ||
| 82 | } | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Handle restricted content appropriately. | ||
| 86 | * | ||
| 87 | * NOTE. This is only for filtering terms, and should not | ||
| 88 | * be used to redirect or replace the entire page. | ||
| 89 | * | ||
| 90 | * @param \WP_Term[] $terms Array of terms to filter. | ||
| 91 | * @param string $taxonomy The taxonomy. | ||
| 92 | * @param array<string,mixed> $query_vars Array of query vars. | ||
| 93 | * @param \WP_Term_Query $query The WP_Query instance (passed by reference). | ||
| 94 | * | ||
| 95 | * @return \WP_Term[] | ||
| 96 | */ | ||
| 97 | public function restrict_query_terms( $terms, $taxonomy, $query_vars, $query ) { | ||
| 98 | if ( query_can_be_ignored( $query ) ) { | ||
| 99 | return $terms; | ||
| 100 | } | ||
| 101 | |||
| 102 | if ( protection_is_disabled() ) { | ||
| 103 | return $terms; | ||
| 104 | } | ||
| 105 | |||
| 106 | $term_restrictions = get_restriction_matches_for_queried_terms( $query ); | ||
| 107 | |||
| 108 | if ( false === $term_restrictions ) { | ||
| 109 | return $terms; | ||
| 110 | } | ||
| 111 | |||
| 112 | // If we have restrictions on the queried terms, handle them top down. | ||
| 113 | foreach ( $term_restrictions as $match ) { | ||
| 114 | $term_id = $match['term_ids']; | ||
| 115 | $restriction = $match['restriction']; | ||
| 116 | |||
| 117 | /** | ||
| 118 | * Use this filter to prevent a term from being restricted, or to handle it yourself. | ||
| 119 | * | ||
| 120 | * @param null|mixed $pre Whether to prevent the term from being restricted. | ||
| 121 | * @param null|\ContentControl\Models\Restriction $restriction Restriction object. | ||
| 122 | * @param int[] $term_id Term ID. | ||
| 123 | * @return null|mixed | ||
| 124 | */ | ||
| 125 | if ( null !== apply_filters( 'content_control/pre_restrict_archive_term', null, $restriction, $term_id ) ) { | ||
| 126 | continue; | ||
| 127 | } | ||
| 128 | |||
| 129 | /** | ||
| 130 | * Fires when a term is restricted, but before the restriction is handled. | ||
| 131 | * | ||
| 132 | * @param \ContentControl\Models\Restriction $restriction Restriction object. | ||
| 133 | * @param int[] $term_id Perm ID. | ||
| 134 | */ | ||
| 135 | do_action( 'content_control/restrict_archive_term', $restriction, $term_id ); | ||
| 136 | |||
| 137 | $handling = $restriction->get_setting( 'additionalQueryHandling' ); | ||
| 138 | |||
| 139 | switch ( $handling ) { | ||
| 140 | case 'hide': | ||
| 141 | foreach ( $terms as $key => $term ) { | ||
| 142 | if ( in_array( $term->term_id, $term_id, true ) ) { | ||
| 143 | unset( $terms[ $key ] ); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | // Reset term indexes. | ||
| 148 | $query->terms = array_values( $terms ); | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | return $terms; | ||
| 154 | } | ||
| 155 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend Rest API query restrictions. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Frontend\Restrictions; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | use function ContentControl\content_is_restricted; | ||
| 14 | use function ContentControl\protection_is_disabled; | ||
| 15 | use function ContentControl\get_applicable_restriction; | ||
| 16 | |||
| 17 | defined( 'ABSPATH' ) || exit; | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Class for handling global restrictions of the Rest API. | ||
| 21 | * | ||
| 22 | * @package ContentControl | ||
| 23 | */ | ||
| 24 | class RestAPI extends Controller { | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Initiate functionality. | ||
| 28 | * | ||
| 29 | * @return void | ||
| 30 | */ | ||
| 31 | public function init() { | ||
| 32 | add_filter( 'rest_pre_dispatch', [ $this, 'pre_dispatch' ], 1, 3 ); | ||
| 33 | } | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Handle a restriction on the rest api via pre_dispatch. | ||
| 37 | * | ||
| 38 | * @param mixed $result Response to replace the requested resource with. Can be anything a normal endpoint can return, or null to not hijack the request. | ||
| 39 | * @param mixed $server Server instance. | ||
| 40 | * @param mixed $request Request used to generate the response. | ||
| 41 | * | ||
| 42 | * @return mixed | ||
| 43 | */ | ||
| 44 | public function pre_dispatch( $result, $server, $request ) { // phpcs:ignore | ||
| 45 | if ( protection_is_disabled() ) { | ||
| 46 | return $result; | ||
| 47 | } | ||
| 48 | |||
| 49 | if ( content_is_restricted() ) { | ||
| 50 | $restriction = get_applicable_restriction(); | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Fires when a post is restricted, but before the restriction is handled. | ||
| 54 | * | ||
| 55 | * @param \ContentControl\Models\Restriction $restriction Restriction object. | ||
| 56 | */ | ||
| 57 | do_action( 'content_control/restrict_rest_query', $restriction ); | ||
| 58 | |||
| 59 | $method = $restriction->get_setting( 'restApiQueryHandling', 'forbidden' ); | ||
| 60 | |||
| 61 | switch ( $method ) { | ||
| 62 | // If we got here, the default is to return a rest_forbidden response. | ||
| 63 | case 'forbidden': | ||
| 64 | // Mimic a rest_forbidden response. | ||
| 65 | return new \WP_Error( | ||
| 66 | 'rest_forbidden', | ||
| 67 | $restriction->get_setting( 'restApiQueryMessage', __( 'You do not have permission to do this.', 'content-control' ), ), | ||
| 68 | [ 'status' => 403 ] | ||
| 69 | ); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | return $result; | ||
| 74 | } | ||
| 75 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Frontend feed setup. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers\Frontend; | ||
| 10 | |||
| 11 | defined( 'ABSPATH' ) || exit; | ||
| 12 | |||
| 13 | use ContentControl\Base\Controller; | ||
| 14 | |||
| 15 | use WP_Customize_Manager; | ||
| 16 | |||
| 17 | use function ContentControl\is_rest; | ||
| 18 | use function ContentControl\protection_is_disabled; | ||
| 19 | use function ContentControl\user_meets_requirements; | ||
| 20 | use function ContentControl\Widgets\get_options as get_widget_options; | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Class ContentControl\Frontend\Widgets | ||
| 24 | */ | ||
| 25 | class Widgets extends Controller { | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Initialize Widgets Frontend. | ||
| 29 | */ | ||
| 30 | public function init() { | ||
| 31 | add_filter( 'sidebars_widgets', [ $this, 'exclude_widgets' ] ); | ||
| 32 | } | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Checks for and excludes widgets based on their chosen options. | ||
| 36 | * | ||
| 37 | * @param array<string,array<string>> $widget_areas An array of widget areas and their widgets. | ||
| 38 | * | ||
| 39 | * @return array<string,array<string>> The modified $widget_area array. | ||
| 40 | */ | ||
| 41 | public function exclude_widgets( $widget_areas ) { | ||
| 42 | if ( is_rest() || protection_is_disabled() || $this->is_customize_preview() ) { | ||
| 43 | return $widget_areas; | ||
| 44 | } | ||
| 45 | |||
| 46 | foreach ( $widget_areas as $widget_area => $widgets ) { | ||
| 47 | if ( ! empty( $widgets ) && 'wp_inactive_widgets' !== $widget_area ) { | ||
| 48 | foreach ( $widgets as $position => $widget_id ) { | ||
| 49 | $options = get_widget_options( $widget_id ); | ||
| 50 | |||
| 51 | // If no options, then skip this one. | ||
| 52 | if ( empty( $options['which_users'] ) ) { | ||
| 53 | continue; | ||
| 54 | } | ||
| 55 | |||
| 56 | // If not accessible then exclude this item. | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Filter whether to exclude a widget. | ||
| 60 | * | ||
| 61 | * @param bool $exclude Whether to exclude the widget. | ||
| 62 | * @param array $options Widget options. | ||
| 63 | * @param string $widget_id Widget ID. | ||
| 64 | * | ||
| 65 | * @return bool | ||
| 66 | */ | ||
| 67 | $exclude = apply_filters( | ||
| 68 | 'content_control/should_exclude_widget', | ||
| 69 | ! user_meets_requirements( $options['which_users'], $options['roles'] ), | ||
| 70 | $options, | ||
| 71 | $widget_id | ||
| 72 | ); | ||
| 73 | |||
| 74 | // unset non-visible item. | ||
| 75 | if ( $exclude ) { | ||
| 76 | unset( $widget_areas[ $widget_area ][ $position ] ); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | return $widget_areas; | ||
| 83 | } | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Is customizer. | ||
| 87 | * | ||
| 88 | * @return boolean | ||
| 89 | */ | ||
| 90 | public function is_customize_preview() { | ||
| 91 | global $wp_customize; | ||
| 92 | |||
| 93 | return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview(); | ||
| 94 | } | ||
| 95 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Post type setup. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Post type controller. | ||
| 15 | */ | ||
| 16 | class PostTypes extends Controller { | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Init controller. | ||
| 20 | * | ||
| 21 | * @return void | ||
| 22 | */ | ||
| 23 | public function init() { | ||
| 24 | add_action( 'init', [ $this, 'register_post_type' ] ); | ||
| 25 | add_action( 'init', [ $this, 'register_rest_fields' ] ); | ||
| 26 | add_action( 'save_post_cc_restriction', [ $this, 'save_post' ], 10, 3 ); | ||
| 27 | add_filter( 'rest_pre_dispatch', [ $this, 'rest_pre_dispatch' ], 10, 3 ); | ||
| 28 | add_filter( 'content_control/sanitize_restriction_settings', [ $this, 'sanitize_restriction_settings' ], 10, 2 ); | ||
| 29 | add_filter( 'content_control/validate_restriction_settings', [ $this, 'validate_restriction_settings' ], 10, 2 ); | ||
| 30 | } | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Register `restriction` post type. | ||
| 34 | * | ||
| 35 | * @return void | ||
| 36 | */ | ||
| 37 | public function register_post_type() { | ||
| 38 | /** | ||
| 39 | * Post Type: Restrictions. | ||
| 40 | */ | ||
| 41 | $labels = [ | ||
| 42 | 'name' => __( 'Restrictions', 'content-control' ), | ||
| 43 | 'singular_name' => __( 'Restriction', 'content-control' ), | ||
| 44 | ]; | ||
| 45 | |||
| 46 | $args = [ | ||
| 47 | 'label' => __( 'Restrictions', 'content-control' ), | ||
| 48 | 'labels' => $labels, | ||
| 49 | 'description' => '', | ||
| 50 | 'public' => false, | ||
| 51 | 'publicly_queryable' => false, | ||
| 52 | 'show_ui' => false, | ||
| 53 | 'show_in_rest' => true, | ||
| 54 | 'rest_base' => 'restrictions', | ||
| 55 | 'rest_namespace' => 'content-control/v2', | ||
| 56 | 'has_archive' => false, | ||
| 57 | 'show_in_menu' => false, | ||
| 58 | 'show_in_nav_menus' => false, | ||
| 59 | 'delete_with_user' => false, | ||
| 60 | 'exclude_from_search' => true, | ||
| 61 | 'map_meta_cap' => true, | ||
| 62 | 'hierarchical' => false, | ||
| 63 | 'can_export' => true, | ||
| 64 | 'rewrite' => false, | ||
| 65 | 'query_var' => false, | ||
| 66 | 'supports' => [ | ||
| 67 | 'title', | ||
| 68 | 'excerpt', | ||
| 69 | // 'editor', | ||
| 70 | ], | ||
| 71 | 'show_in_graphql' => false, | ||
| 72 | 'capabilities' => [ | ||
| 73 | 'create_posts' => $this->container->get_permission( 'edit_restrictions' ), | ||
| 74 | 'edit_posts' => $this->container->get_permission( 'edit_restrictions' ), | ||
| 75 | 'delete_posts' => $this->container->get_permission( 'edit_restrictions' ), | ||
| 76 | ], | ||
| 77 | ]; | ||
| 78 | |||
| 79 | register_post_type( 'cc_restriction', $args ); | ||
| 80 | } | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Registers custom REST API fields for cc_restrictions post type. | ||
| 84 | * | ||
| 85 | * @return void | ||
| 86 | */ | ||
| 87 | public function register_rest_fields() { | ||
| 88 | register_rest_field( 'cc_restriction', 'settings', [ | ||
| 89 | 'get_callback' => function ( $obj, $field, $request ) { | ||
| 90 | $settings = get_post_meta( $obj['id'], 'restriction_settings', true ); | ||
| 91 | |||
| 92 | // Backfill from content if empty. | ||
| 93 | if ( empty( $settings['customMessage'] ) ) { | ||
| 94 | $settings['customMessage'] = get_post_field( 'post_content', $obj['id'], 'raw' ); | ||
| 95 | } | ||
| 96 | |||
| 97 | if ( ! empty( $settings['customMessage'] ) ) { | ||
| 98 | // Change output based on context. | ||
| 99 | $settings['customMessage'] = 'edit' === $request->get_param( 'context' ) ? | ||
| 100 | sanitize_post_field( 'post_content', $settings['customMessage'], $obj['id'], 'raw' ) : | ||
| 101 | sanitize_post_field( 'post_content', $settings['customMessage'], $obj['id'], 'display' ); | ||
| 102 | } | ||
| 103 | |||
| 104 | return $settings; | ||
| 105 | }, | ||
| 106 | 'update_callback' => function ( $value, $obj ) { | ||
| 107 | $custom_message = ! empty( $value['customMessage'] ) ? $value['customMessage'] : ''; | ||
| 108 | |||
| 109 | // Save custom message to restriction content for now. | ||
| 110 | wp_update_post( [ | ||
| 111 | 'ID' => $obj->ID, | ||
| 112 | 'post_content' => $custom_message, | ||
| 113 | ] ); | ||
| 114 | |||
| 115 | // Update the field/meta value. | ||
| 116 | update_post_meta( $obj->ID, 'restriction_settings', $value ); | ||
| 117 | }, | ||
| 118 | 'schema' => [ | ||
| 119 | 'type' => 'object', | ||
| 120 | 'arg_options' => [ | ||
| 121 | 'sanitize_callback' => function ( $settings, $request ) { | ||
| 122 | /** | ||
| 123 | * Sanitize the restriction settings. | ||
| 124 | * | ||
| 125 | * @param array<string,mixed> $settings The settings to sanitize. | ||
| 126 | * @param int $id The restriction ID. | ||
| 127 | * @param \WP_REST_Request $request The request object. | ||
| 128 | * | ||
| 129 | * @return array<string,mixed> The sanitized settings. | ||
| 130 | */ | ||
| 131 | return apply_filters( 'content_control/sanitize_restriction_settings', $settings, $request->get_param( 'id' ), $request ); | ||
| 132 | }, | ||
| 133 | 'validate_callback' => function ( $settings, $request ) { | ||
| 134 | /** | ||
| 135 | * Validate the restriction settings. | ||
| 136 | * | ||
| 137 | * @param array<string,mixed> $settings The settings to validate. | ||
| 138 | * @param int $id The restriction ID. | ||
| 139 | * @param \WP_REST_Request $request The request object. | ||
| 140 | * | ||
| 141 | * @return bool|\WP_Error True if valid, WP_Error if not. | ||
| 142 | */ | ||
| 143 | return apply_filters( 'content_control/validate_restriction_settings', $settings, $request->get_param( 'id' ), $request ); | ||
| 144 | }, | ||
| 145 | ], | ||
| 146 | ], | ||
| 147 | 'permission_callback' => function () { | ||
| 148 | return current_user_can( $this->container->get_permission( 'edit_restrictions' ) ); | ||
| 149 | }, | ||
| 150 | ] ); | ||
| 151 | |||
| 152 | register_rest_field( 'cc_restriction', 'priority', [ | ||
| 153 | 'get_callback' => function ( $obj ) { | ||
| 154 | return (int) get_post_field( 'menu_order', $obj['id'], 'raw' ); | ||
| 155 | }, | ||
| 156 | 'update_callback' => function ( $value, $obj ) { | ||
| 157 | wp_update_post( [ | ||
| 158 | 'ID' => $obj->ID, | ||
| 159 | 'menu_order' => $value, | ||
| 160 | ] ); | ||
| 161 | }, | ||
| 162 | 'permission_callback' => function () { | ||
| 163 | return current_user_can( $this->container->get_permission( 'edit_restrictions' ) ); | ||
| 164 | }, | ||
| 165 | 'schema' => [ | ||
| 166 | 'type' => 'integer', | ||
| 167 | 'arg_options' => [ | ||
| 168 | 'sanitize_callback' => function ( $priority ) { | ||
| 169 | return absint( $priority ); | ||
| 170 | }, | ||
| 171 | 'validate_callback' => function ( $priority ) { | ||
| 172 | return is_int( $priority ); | ||
| 173 | }, | ||
| 174 | ], | ||
| 175 | ], | ||
| 176 | ] ); | ||
| 177 | |||
| 178 | register_rest_field( 'cc_restriction', 'data_version', [ | ||
| 179 | 'get_callback' => function ( $obj ) { | ||
| 180 | return get_post_meta( $obj['id'], 'data_version', true ); | ||
| 181 | }, | ||
| 182 | 'update_callback' => function ( $value, $obj ) { | ||
| 183 | // Update the field/meta value. | ||
| 184 | update_post_meta( $obj->ID, 'data_version', $value ); | ||
| 185 | }, | ||
| 186 | 'permission_callback' => function () { | ||
| 187 | return current_user_can( $this->container->get_permission( 'edit_restrictions' ) ); | ||
| 188 | }, | ||
| 189 | ] ); | ||
| 190 | } | ||
| 191 | |||
| 192 | /** | ||
| 193 | * Sanitize restriction settings. | ||
| 194 | * | ||
| 195 | * @param array<string,mixed> $settings The settings to sanitize. | ||
| 196 | * @param int $id The restriction ID. | ||
| 197 | * | ||
| 198 | * @return array<string,mixed> The sanitized settings. | ||
| 199 | */ | ||
| 200 | public function sanitize_restriction_settings( $settings, $id ) { | ||
| 201 | |||
| 202 | // Sanitize custom message. | ||
| 203 | if ( ! empty( $settings['customMessage'] ) ) { | ||
| 204 | $settings['customMessage'] = sanitize_post_field( 'post_content', $settings['customMessage'], $id, 'db' ); | ||
| 205 | } | ||
| 206 | |||
| 207 | return $settings; | ||
| 208 | } | ||
| 209 | |||
| 210 | /** | ||
| 211 | * Validate restriction settings. | ||
| 212 | * | ||
| 213 | * @param array<string,mixed> $settings The settings to validate. | ||
| 214 | * @param int $id The restriction ID. | ||
| 215 | * | ||
| 216 | * @return bool|\WP_Error True if valid, WP_Error if not. | ||
| 217 | */ | ||
| 218 | public function validate_restriction_settings( $settings, $id ) { | ||
| 219 | // TODO Validate all known settings by type. | ||
| 220 | return true; | ||
| 221 | } | ||
| 222 | |||
| 223 | |||
| 224 | /** | ||
| 225 | * Add data version meta to new restrictions. | ||
| 226 | * | ||
| 227 | * @param int $post_id Post ID. | ||
| 228 | * @param \WP_Post $post Post object. | ||
| 229 | * @param bool $update Whether this is an existing post being updated or not. | ||
| 230 | * | ||
| 231 | * @return void | ||
| 232 | */ | ||
| 233 | public function save_post( $post_id, $post, $update ) { | ||
| 234 | if ( $update ) { | ||
| 235 | return; | ||
| 236 | } | ||
| 237 | |||
| 238 | add_post_meta( $post_id, 'data_version', 1 ); | ||
| 239 | } | ||
| 240 | |||
| 241 | /** | ||
| 242 | * Prevent access to restrictions endpoint. | ||
| 243 | * | ||
| 244 | * @param mixed $result Response to replace the requested version with. | ||
| 245 | * @param \WP_REST_Server $server Server instance. | ||
| 246 | * @param \WP_REST_Request<array<string,mixed>> $request Request used to generate the response. | ||
| 247 | * @return mixed | ||
| 248 | */ | ||
| 249 | public function rest_pre_dispatch( $result, $server, $request ) { | ||
| 250 | // Get the route being requested. | ||
| 251 | $route = $request->get_route(); | ||
| 252 | |||
| 253 | // Only proceed if we're creating a user. | ||
| 254 | if ( false === strpos( $route, '/content-control/v2/restrictions' ) ) { | ||
| 255 | return $result; | ||
| 256 | } | ||
| 257 | |||
| 258 | $current_user_can = current_user_can( $this->container->get_permission( 'edit_restrictions' ) ); | ||
| 259 | |||
| 260 | // Prevent discovery of the endpoints data from unauthorized users. | ||
| 261 | if ( ! $current_user_can ) { | ||
| 262 | return new \WP_Error( | ||
| 263 | 'rest_forbidden', | ||
| 264 | __( 'Access to this endpoint requires authorization.', 'content-control' ), | ||
| 265 | [ | ||
| 266 | 'status' => rest_authorization_required_code(), | ||
| 267 | ] | ||
| 268 | ); | ||
| 269 | } | ||
| 270 | |||
| 271 | // Return data to the client to parse. | ||
| 272 | return $result; | ||
| 273 | } | ||
| 274 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * RestAPI blocks setup. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers; | ||
| 10 | |||
| 11 | defined( 'ABSPATH' ) || exit; | ||
| 12 | |||
| 13 | use ContentControl\Base\Controller; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * RestAPI function initialization. | ||
| 17 | */ | ||
| 18 | class RestAPI extends Controller { | ||
| 19 | /** | ||
| 20 | * Initiate rest api integrations. | ||
| 21 | */ | ||
| 22 | public function init() { | ||
| 23 | add_action( 'rest_api_init', [ $this, 'register_routes' ] ); | ||
| 24 | } | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Register Rest API routes. | ||
| 28 | * | ||
| 29 | * @return void | ||
| 30 | */ | ||
| 31 | public function register_routes() { | ||
| 32 | ( new \ContentControl\RestAPI\BlockTypes() )->register_routes(); | ||
| 33 | ( new \ContentControl\RestAPI\License() )->register_routes(); | ||
| 34 | ( new \ContentControl\RestAPI\ObjectSearch() )->register_routes(); | ||
| 35 | ( new \ContentControl\RestAPI\Settings() )->register_routes(); | ||
| 36 | } | ||
| 37 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Shortcode setup. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * @package ContentControl | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Controllers; | ||
| 10 | |||
| 11 | use ContentControl\Base\Controller; | ||
| 12 | |||
| 13 | use function ContentControl\user_meets_requirements; | ||
| 14 | |||
| 15 | defined( 'ABSPATH' ) || exit; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Class Shortcodes | ||
| 19 | * | ||
| 20 | * @package ContentControl | ||
| 21 | */ | ||
| 22 | class Shortcodes extends Controller { | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Initialize Widgets | ||
| 26 | */ | ||
| 27 | public function init() { | ||
| 28 | add_shortcode( 'content_control', [ $this, 'content_control' ] ); | ||
| 29 | } | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Process the [content_control] shortcode. | ||
| 33 | * | ||
| 34 | * @param array<string,string|int|null> $atts Array or shortcode attributes. | ||
| 35 | * @param string $content Content inside shortcode. | ||
| 36 | * | ||
| 37 | * @return string | ||
| 38 | */ | ||
| 39 | public function content_control( $atts, $content = '' ) { | ||
| 40 | // Deprecated. | ||
| 41 | $deprecated_atts = shortcode_atts( [ | ||
| 42 | 'logged_out' => null, // @deprecated 2.0. | ||
| 43 | 'roles' => null, // @deprecated 2.0. | ||
| 44 | ], $atts ); | ||
| 45 | |||
| 46 | $atts = shortcode_atts( [ | ||
| 47 | 'status' => 'logged_in', // 'logged_in' or 'logged_out | ||
| 48 | 'allowed_roles' => null, | ||
| 49 | 'excluded_roles' => null, | ||
| 50 | 'class' => '', | ||
| 51 | 'message' => $this->container->get_option( 'defaultDenialMessage', '' ), | ||
| 52 | ], $this->normalize_empty_atts( $atts ), 'content_control' ); | ||
| 53 | |||
| 54 | // Handle old args. | ||
| 55 | if ( isset( $deprecated_atts['logged_out'] ) ) { | ||
| 56 | $atts['status'] = (bool) $deprecated_atts['logged_out'] ? 'logged_out' : 'logged_in'; | ||
| 57 | } | ||
| 58 | |||
| 59 | if ( isset( $deprecated_atts['roles'] ) && ! empty( $deprecated_atts['roles'] ) ) { | ||
| 60 | $atts['allowed_roles'] = $deprecated_atts['roles']; | ||
| 61 | } | ||
| 62 | |||
| 63 | $user_roles = []; | ||
| 64 | $match_type = 'any'; | ||
| 65 | |||
| 66 | // Normalize args. | ||
| 67 | if ( ! empty( $atts['excluded_roles'] ) ) { | ||
| 68 | $user_roles = $atts['excluded_roles']; | ||
| 69 | $match_type = 'exclude'; | ||
| 70 | } elseif ( ! empty( $atts['allowed_roles'] ) ) { | ||
| 71 | $user_roles = $atts['allowed_roles']; | ||
| 72 | $match_type = 'match'; | ||
| 73 | } | ||
| 74 | |||
| 75 | // Convert classes to array. | ||
| 76 | $classes = ! empty( $atts['class'] ) ? explode( ' ', $atts['class'] ) : []; | ||
| 77 | |||
| 78 | $classes[] = 'content-control-container'; | ||
| 79 | // @deprecated 2.0.0 | ||
| 80 | $classes[] = 'jp-cc'; | ||
| 81 | |||
| 82 | if ( user_meets_requirements( $atts['status'], $user_roles, $match_type ) ) { | ||
| 83 | $classes[] = 'content-control-accessible'; | ||
| 84 | // @deprecated 2.0.0 | ||
| 85 | $classes[] = 'jp-cc-accessible'; | ||
| 86 | $container = '<div class="%1$s">%2$s</div>'; | ||
| 87 | } else { | ||
| 88 | $classes[] = 'content-control-not-accessible'; | ||
| 89 | // @deprecated 2.0.0 | ||
| 90 | $classes[] = 'jp-cc-not-accessible'; | ||
| 91 | $container = '<div class="%1$s">%3$s</div>'; | ||
| 92 | } | ||
| 93 | |||
| 94 | $classes = implode( ' ', $classes ); | ||
| 95 | |||
| 96 | return sprintf( $container, esc_attr( $classes ), do_shortcode( $content ), do_shortcode( $atts['message'] ) ); | ||
| 97 | } | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Takes set but empty attributes and sets them to true. | ||
| 101 | * | ||
| 102 | * These are typically valueless boolean attributes. | ||
| 103 | * | ||
| 104 | * @param array<string|int,string|int|null> $atts Array of shortcode attributes. | ||
| 105 | * | ||
| 106 | * @return (int|null|string|true)[] | ||
| 107 | * | ||
| 108 | * @psalm-return array<int|string, int|null|string|true> | ||
| 109 | */ | ||
| 110 | public function normalize_empty_atts( $atts = [] ) { | ||
| 111 | if ( ! is_array( $atts ) || empty( $atts ) ) { | ||
| 112 | $atts = []; | ||
| 113 | } | ||
| 114 | |||
| 115 | foreach ( $atts as $attribute => $value ) { | ||
| 116 | if ( is_int( $attribute ) ) { | ||
| 117 | $atts[ strtolower( $value ) ] = true; | ||
| 118 | unset( $atts[ $attribute ] ); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | return $atts; | ||
| 123 | } | ||
| 124 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * TrustedLogin. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2023, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Controllers; | ||
| 11 | |||
| 12 | use ContentControl\Base\Controller; | ||
| 13 | use ContentControl\Vendor\TrustedLogin\Client; | ||
| 14 | use ContentControl\Vendor\TrustedLogin\Config; | ||
| 15 | |||
| 16 | defined( 'ABSPATH' ) || exit; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * TrustedLogin. | ||
| 20 | * | ||
| 21 | * @package ContentControl | ||
| 22 | */ | ||
| 23 | class TrustedLogin extends Controller { | ||
| 24 | |||
| 25 | /** | ||
| 26 | * TrustedLogin init. | ||
| 27 | */ | ||
| 28 | public function init() { | ||
| 29 | $this->hooks(); | ||
| 30 | |||
| 31 | $config = [ | ||
| 32 | 'auth' => [ | ||
| 33 | 'api_key' => 'f97f5be6e02d1565', | ||
| 34 | 'license_key' => $this->container->get( 'license' )->get_license_key(), | ||
| 35 | ], | ||
| 36 | 'vendor' => [ | ||
| 37 | 'namespace' => 'content-control', | ||
| 38 | 'title' => 'Content Control', | ||
| 39 | 'display_name' => 'Content Control Support', | ||
| 40 | 'logo_url' => $this->container->get_url( 'assets/images/logo.svg' ), | ||
| 41 | 'email' => 'support+{hash}@contentcontrolplugin.com', | ||
| 42 | 'website' => 'https://contentcontrolplugin.com?utm_campaign=grant-access&utm_source=plugin-settings-page&utm_medium=plugin-ui&utm_content=grant-access-title-link', | ||
| 43 | 'support_url' => 'https://contentcontrolplugin.com/support/?utm_campaign=grant-access&utm_source=plugin-settings-page&utm_medium=plugin-ui&utm_content=support-footer-link', | ||
| 44 | ], | ||
| 45 | 'role' => 'administrator', | ||
| 46 | 'caps' => [ | ||
| 47 | 'add' => [ | ||
| 48 | $this->container->get_permission( 'manage_settings' ) => __( 'This allows us to check your global restrictions and plugin settings.', 'content-control' ), | ||
| 49 | $this->container->get_permission( 'edit_block_controls' ) => __( 'This allows us to check your block control settings.', 'content-control' ), | ||
| 50 | ], | ||
| 51 | 'remove' => [ | ||
| 52 | 'delete_published_pages' => 'Your published posts cannot and will not be deleted by support staff', | ||
| 53 | 'manage_woocommerce' => 'We don\'t need to manage your shop!', | ||
| 54 | ], | ||
| 55 | ], | ||
| 56 | 'decay' => WEEK_IN_SECONDS, | ||
| 57 | 'menu' => [ | ||
| 58 | 'slug' => false, | ||
| 59 | ], | ||
| 60 | 'logging' => [ | ||
| 61 | 'enabled' => false, | ||
| 62 | ], | ||
| 63 | 'require_ssl' => false, | ||
| 64 | 'webhook' => [ | ||
| 65 | 'url' => null, | ||
| 66 | 'debug_data' => false, | ||
| 67 | 'create_ticket' => false, | ||
| 68 | ], | ||
| 69 | 'paths' => [ | ||
| 70 | 'js' => $this->container->get_url( 'vendor-prefixed/trustedlogin/client/src/assets/trustedlogin.js' ), | ||
| 71 | 'css' => $this->container->get_url( 'dist/settings-page.css' ), | ||
| 72 | ], | ||
| 73 | ]; | ||
| 74 | |||
| 75 | try { | ||
| 76 | new Client( | ||
| 77 | new Config( $config ) | ||
| 78 | ); | ||
| 79 | } catch ( \Exception $exception ) { | ||
| 80 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | ||
| 81 | \error_log( $exception->getMessage() ); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | /** | ||
| 86 | * Hooks. | ||
| 87 | * | ||
| 88 | * @return void | ||
| 89 | */ | ||
| 90 | public function hooks() { | ||
| 91 | add_action( 'admin_menu', [ $this, 'admin_menu' ] ); | ||
| 92 | } | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Admin menu. | ||
| 96 | * | ||
| 97 | * @return void | ||
| 98 | */ | ||
| 99 | public function admin_menu() { | ||
| 100 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended | ||
| 101 | if ( ! isset( $_GET['page'] ) || 'grant-content-control-access' !== $_GET['page'] ) { | ||
| 102 | return; | ||
| 103 | } | ||
| 104 | |||
| 105 | add_options_page( | ||
| 106 | __( 'Content Control Support Access', 'content-control' ), | ||
| 107 | __( 'Content Control Support Access', 'content-control' ), | ||
| 108 | $this->container->get_permission( 'manage_settings' ), | ||
| 109 | 'grant-content-control-access', | ||
| 110 | function () { | ||
| 111 | // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores | ||
| 112 | do_action( 'trustedlogin/content-control/auth_screen' ); | ||
| 113 | } | ||
| 114 | ); | ||
| 115 | } | ||
| 116 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Install_Skin class. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | * @subpackage Installers | ||
| 7 | * @since 2.0.0 | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Installers; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * Skin for on-the-fly addon installations. | ||
| 14 | * | ||
| 15 | * @since 1.0.0 | ||
| 16 | * @since 2.0.0 Extend PluginSilentUpgraderSkin and clean up the class. | ||
| 17 | */ | ||
| 18 | class Install_Skin extends PluginSilentUpgraderSkin { | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Instead of outputting HTML for errors, json_encode the errors and send them | ||
| 22 | * back to the Ajax script for processing. | ||
| 23 | * | ||
| 24 | * @since 2.0.0 | ||
| 25 | * | ||
| 26 | * @param string|\WP_Error $errors Array of errors with the install process. | ||
| 27 | */ | ||
| 28 | public function error( $errors ) { | ||
| 29 | if ( ! empty( $errors ) ) { | ||
| 30 | return wp_send_json_error( $errors ); | ||
| 31 | } | ||
| 32 | |||
| 33 | return $errors; | ||
| 34 | } | ||
| 35 | } |
This diff is collapsed.
Click to expand it.
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * PluginSilentUpgraderSkin class. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | * @subpackage Installers | ||
| 7 | * @since 2.0.0 | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Installers; | ||
| 11 | |||
| 12 | /** \WP_Upgrader_Skin class */ | ||
| 13 | require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php'; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Class PluginSilentUpgraderSkin. | ||
| 17 | * | ||
| 18 | * @internal Please do not use this class outside of core plugin development. May be removed at any time. | ||
| 19 | * | ||
| 20 | * @since 2.0.0 | ||
| 21 | */ | ||
| 22 | class PluginSilentUpgraderSkin extends \WP_Upgrader_Skin { | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Empty out the header of its HTML content and only check to see if it has | ||
| 26 | * been performed or not. | ||
| 27 | * | ||
| 28 | * @return void | ||
| 29 | */ | ||
| 30 | public function header() { | ||
| 31 | } | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Empty out the footer of its HTML contents. | ||
| 35 | * | ||
| 36 | * @return void | ||
| 37 | */ | ||
| 38 | public function footer() { | ||
| 39 | } | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Instead of outputting HTML for errors, just return them. | ||
| 43 | * Ajax request will just ignore it. | ||
| 44 | * | ||
| 45 | * @param string|\WP_Error $errors Array of errors with the install process. | ||
| 46 | * | ||
| 47 | * @return string|\WP_Error | ||
| 48 | */ | ||
| 49 | public function error( $errors ) { | ||
| 50 | return $errors; | ||
| 51 | } | ||
| 52 | |||
| 53 | /** | ||
| 54 | * Empty out JavaScript output that calls function to decrement the update counts. | ||
| 55 | * | ||
| 56 | * @param string $type Type of update count to decrement. | ||
| 57 | * | ||
| 58 | * @return void | ||
| 59 | */ | ||
| 60 | public function decrement_update_count( $type ) { | ||
| 61 | } | ||
| 62 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Plugin container. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Interfaces; | ||
| 11 | |||
| 12 | defined( 'ABSPATH' ) || exit; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Localized controller class. | ||
| 16 | */ | ||
| 17 | interface Controller { | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Handle hooks & filters or various other init tasks. | ||
| 21 | * | ||
| 22 | * @return void | ||
| 23 | */ | ||
| 24 | public function init(); | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Check if controller is enabled. | ||
| 28 | * | ||
| 29 | * @return bool | ||
| 30 | */ | ||
| 31 | public function controller_enabled(); | ||
| 32 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Plugin upgrade. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Interfaces; | ||
| 11 | |||
| 12 | defined( 'ABSPATH' ) || exit; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Localized controller class. | ||
| 16 | */ | ||
| 17 | interface Upgrade { | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Return label for this upgrade. | ||
| 21 | * | ||
| 22 | * @return string | ||
| 23 | */ | ||
| 24 | public function label(); | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Return full description for this upgrade. | ||
| 28 | * | ||
| 29 | * @return string | ||
| 30 | */ | ||
| 31 | public function description(); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Check if this upgrade is required. | ||
| 35 | * | ||
| 36 | * @return bool | ||
| 37 | */ | ||
| 38 | public function is_required(); | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Check if prerequisites are met. | ||
| 42 | * | ||
| 43 | * @return bool | ||
| 44 | */ | ||
| 45 | public function prerequisites_met(); | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Run the upgrade. | ||
| 49 | * | ||
| 50 | * @return void|\WP_Error|false | ||
| 51 | */ | ||
| 52 | public function run(); | ||
| 53 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Restriction model. | ||
| 4 | * | ||
| 5 | * @package ContentControl\RuleEngine | ||
| 6 | * @subpackage Models | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Models; | ||
| 10 | |||
| 11 | use ContentControl\Models\RuleEngine\Query; | ||
| 12 | |||
| 13 | use function ContentControl\fetch_key_from_array; | ||
| 14 | use function ContentControl\get_default_restriction_settings; | ||
| 15 | |||
| 16 | defined( 'ABSPATH' ) || exit; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Model for restriction sets. | ||
| 20 | * | ||
| 21 | * @version 3.0.0 | ||
| 22 | * @since 2.1.0 | ||
| 23 | * | ||
| 24 | * @package ContentControl\Models | ||
| 25 | */ | ||
| 26 | class Restriction { | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Current model version. | ||
| 30 | * | ||
| 31 | * @var int | ||
| 32 | */ | ||
| 33 | const MODEL_VERSION = 3; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Post object. | ||
| 37 | * | ||
| 38 | * @var \WP_Post | ||
| 39 | */ | ||
| 40 | private $post; | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Restriction id. | ||
| 44 | * | ||
| 45 | * @var int | ||
| 46 | */ | ||
| 47 | public $id = 0; | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Restriction slug. | ||
| 51 | * | ||
| 52 | * @var string | ||
| 53 | */ | ||
| 54 | public $slug; | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Restriction label. | ||
| 58 | * | ||
| 59 | * @var string | ||
| 60 | */ | ||
| 61 | public $title; | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Restriction description. | ||
| 65 | * | ||
| 66 | * @var string|null | ||
| 67 | */ | ||
| 68 | public $description; | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Restriction Message. | ||
| 72 | * | ||
| 73 | * @var string|null | ||
| 74 | */ | ||
| 75 | public $message; | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Restriction status. | ||
| 79 | * | ||
| 80 | * @var string | ||
| 81 | */ | ||
| 82 | public $status; | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Restriction priority. | ||
| 86 | * | ||
| 87 | * @var int | ||
| 88 | */ | ||
| 89 | public $priority; | ||
| 90 | |||
| 91 | /** | ||
| 92 | * Restriction Condition Query. | ||
| 93 | * | ||
| 94 | * @var Query | ||
| 95 | */ | ||
| 96 | public $query; | ||
| 97 | |||
| 98 | /** | ||
| 99 | * Restriction Settings. | ||
| 100 | * | ||
| 101 | * @var array<string,mixed> | ||
| 102 | */ | ||
| 103 | public $settings; | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Data version. | ||
| 107 | * | ||
| 108 | * @var int | ||
| 109 | */ | ||
| 110 | public $data_version; | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Build a restriction. | ||
| 114 | * | ||
| 115 | * @param \WP_Post|array<string,mixed> $restriction Restriction data. | ||
| 116 | */ | ||
| 117 | public function __construct( $restriction ) { | ||
| 118 | if ( ! is_a( $restriction, '\WP_Post' ) ) { | ||
| 119 | $this->setup_v1_restriction( $restriction ); | ||
| 120 | } else { | ||
| 121 | $this->post = $restriction; | ||
| 122 | |||
| 123 | /** | ||
| 124 | * Restriction settings. | ||
| 125 | * | ||
| 126 | * @var array<string,mixed>|false $settings | ||
| 127 | */ | ||
| 128 | $settings = get_post_meta( $restriction->ID, 'restriction_settings', true ); | ||
| 129 | |||
| 130 | if ( ! $settings ) { | ||
| 131 | $settings = []; | ||
| 132 | } | ||
| 133 | |||
| 134 | $settings = wp_parse_args( | ||
| 135 | $settings, | ||
| 136 | get_default_restriction_settings() | ||
| 137 | ); | ||
| 138 | |||
| 139 | $this->settings = $settings; | ||
| 140 | |||
| 141 | $properties = [ | ||
| 142 | 'id' => $restriction->ID, | ||
| 143 | 'slug' => $restriction->post_name, | ||
| 144 | 'title' => $restriction->post_title, | ||
| 145 | 'status' => $restriction->post_status, | ||
| 146 | 'priority' => $restriction->menu_order, | ||
| 147 | // We set this late.. on first use. | ||
| 148 | 'description' => null, | ||
| 149 | 'message' => null, | ||
| 150 | ]; | ||
| 151 | |||
| 152 | foreach ( $properties as $key => $value ) { | ||
| 153 | $this->$key = $value; | ||
| 154 | } | ||
| 155 | |||
| 156 | $this->data_version = get_post_meta( $restriction->ID, 'data_version', true ); | ||
| 157 | |||
| 158 | if ( ! $this->data_version ) { | ||
| 159 | $this->data_version = 2; | ||
| 160 | update_post_meta( $restriction->ID, 'data_version', 2 ); | ||
| 161 | } | ||
| 162 | |||
| 163 | $this->query = new Query( $this->get_setting( 'conditions' ) ); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | /** | ||
| 168 | * Map old v1 restriction to new v2 restriction object. | ||
| 169 | * | ||
| 170 | * @param array<string,mixed> $restriction Restriction data. | ||
| 171 | * | ||
| 172 | * @return void | ||
| 173 | */ | ||
| 174 | public function setup_v1_restriction( $restriction ) { | ||
| 175 | static $index = 0; | ||
| 176 | |||
| 177 | $restriction = \wp_parse_args( $restriction, [ | ||
| 178 | 'title' => '', | ||
| 179 | 'who' => '', | ||
| 180 | 'roles' => [], | ||
| 181 | 'protection_method' => 'redirect', | ||
| 182 | 'show_excerpts' => false, | ||
| 183 | 'override_default_message' => false, | ||
| 184 | 'custom_message' => '', | ||
| 185 | 'redirect_type' => 'login', | ||
| 186 | 'redirect_url' => '', | ||
| 187 | 'conditions' => '', | ||
| 188 | ] ); | ||
| 189 | |||
| 190 | $this->data_version = 1; | ||
| 191 | |||
| 192 | $this->id = 0; | ||
| 193 | $this->slug = ''; | ||
| 194 | $this->title = $restriction['title']; | ||
| 195 | $this->description = ''; | ||
| 196 | $this->status = 'publish'; | ||
| 197 | $this->priority = $index; | ||
| 198 | |||
| 199 | $user_roles = is_array( $restriction['roles'] ) ? $restriction['roles'] : []; | ||
| 200 | |||
| 201 | $settings = [ | ||
| 202 | 'userStatus' => $restriction['who'], | ||
| 203 | 'roleMatch' => count( $user_roles ) > 0 ? 'match' : 'any', | ||
| 204 | 'userRoles' => $user_roles, | ||
| 205 | 'protectionMethod' => 'custom_message' === $restriction['protection_method'] ? 'replace' : 'redirect', | ||
| 206 | 'redirectType' => $restriction['redirect_type'], | ||
| 207 | 'redirectUrl' => $restriction['redirect_url'], | ||
| 208 | 'replacementType' => 'message', | ||
| 209 | 'replacementPage' => 0, | ||
| 210 | 'archiveHandling' => 'filter_post_content', | ||
| 211 | 'archiveReplacementPage' => 0, | ||
| 212 | 'archiveRedirectType' => $restriction['redirect_type'], | ||
| 213 | 'archiveRedirectUrl' => $restriction['redirect_url'], | ||
| 214 | 'additionalQueryHandling' => 'filter_post_content', | ||
| 215 | 'overrideMessage' => $restriction['override_default_message'], | ||
| 216 | 'customMessage' => $restriction['custom_message'], | ||
| 217 | 'showExcerpts' => $restriction['show_excerpts'], | ||
| 218 | 'conditions' => \ContentControl\remap_conditions_to_query( $restriction['conditions'] ), | ||
| 219 | ]; | ||
| 220 | |||
| 221 | $this->settings = $settings; | ||
| 222 | |||
| 223 | $this->query = new Query( $settings['conditions'] ); | ||
| 224 | |||
| 225 | ++$index; | ||
| 226 | } | ||
| 227 | |||
| 228 | /** | ||
| 229 | * Get the restriction settings array. | ||
| 230 | * | ||
| 231 | * @return array<string,mixed> | ||
| 232 | */ | ||
| 233 | public function get_settings() { | ||
| 234 | return $this->settings; | ||
| 235 | } | ||
| 236 | |||
| 237 | /** | ||
| 238 | * Get a restriction setting. | ||
| 239 | * | ||
| 240 | * Settings are stored in JS based camelCase. But WP prefers snake_case. | ||
| 241 | * | ||
| 242 | * This method supports camelCase based dot.notation, as well as snake_case. | ||
| 243 | * | ||
| 244 | * @param string $key Setting key. | ||
| 245 | * @param mixed $default_value Default value. | ||
| 246 | * | ||
| 247 | * @return mixed|false | ||
| 248 | */ | ||
| 249 | public function get_setting( $key, $default_value = false ) { | ||
| 250 | // Support camelCase, snake_case, and dot.notation. | ||
| 251 | // Check for camelKeys & dot.notation. | ||
| 252 | $value = \ContentControl\fetch_key_from_array( $key, $this->settings, 'camelCase' ); | ||
| 253 | |||
| 254 | if ( null === $value ) { | ||
| 255 | $value = $default_value; | ||
| 256 | } | ||
| 257 | |||
| 258 | /** | ||
| 259 | * Filter the option. | ||
| 260 | * | ||
| 261 | * @param mixed $value Option value. | ||
| 262 | * @param string $key Option key. | ||
| 263 | * @param mixed $default_value Default value. | ||
| 264 | * @param int $restriction_id Restriction ID. | ||
| 265 | * | ||
| 266 | * @return mixed | ||
| 267 | */ | ||
| 268 | return apply_filters( 'content_control/get_restriction_setting', $value, $key, $default_value, $this->id ); | ||
| 269 | } | ||
| 270 | |||
| 271 | /** | ||
| 272 | * Check if this set has JS based rules. | ||
| 273 | * | ||
| 274 | * @return bool | ||
| 275 | */ | ||
| 276 | public function has_js_rules() { | ||
| 277 | return $this->query->has_js_rules(); | ||
| 278 | } | ||
| 279 | |||
| 280 | /** | ||
| 281 | * Check this sets rules. | ||
| 282 | * | ||
| 283 | * @return bool | ||
| 284 | */ | ||
| 285 | public function check_rules() { | ||
| 286 | if ( ! $this->query->has_rules() ) { | ||
| 287 | // No rules should be treated as no restrictions. | ||
| 288 | return false; | ||
| 289 | } | ||
| 290 | |||
| 291 | return $this->query->check_rules(); | ||
| 292 | } | ||
| 293 | |||
| 294 | /** | ||
| 295 | * Check if this restriction applies to the current user. | ||
| 296 | * | ||
| 297 | * @return bool | ||
| 298 | */ | ||
| 299 | public function user_meets_requirements() { | ||
| 300 | // Filter to allow override user status check early. | ||
| 301 | $bypass = \apply_filters( 'content_control/restriction/bypass_user_requirements', null, $this ); | ||
| 302 | |||
| 303 | if ( null !== $bypass ) { | ||
| 304 | return $bypass; | ||
| 305 | } | ||
| 306 | |||
| 307 | return \ContentControl\user_meets_requirements( $this->get_setting( 'userStatus' ), $this->get_setting( 'userRoles' ), $this->get_setting( 'roleMatch' ) ); | ||
| 308 | } | ||
| 309 | |||
| 310 | /** | ||
| 311 | * Get the description for this restriction. | ||
| 312 | * | ||
| 313 | * @return string | ||
| 314 | */ | ||
| 315 | public function get_description() { | ||
| 316 | if ( ! isset( $this->description ) ) { | ||
| 317 | $this->description = get_the_excerpt( $this->id ); | ||
| 318 | |||
| 319 | if ( empty( $this->description ) ) { | ||
| 320 | $this->description = __( 'This content is restricted.', 'content-control' ); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | return $this->description; | ||
| 325 | } | ||
| 326 | |||
| 327 | /** | ||
| 328 | * Get the message for this restriction. | ||
| 329 | * | ||
| 330 | * @uses \get_the_content() | ||
| 331 | * @uses \ContentControl\get_default_denial_message() | ||
| 332 | * | ||
| 333 | * @param string $context Context. 'display' or 'raw'. | ||
| 334 | * | ||
| 335 | * @return string | ||
| 336 | */ | ||
| 337 | public function get_message( $context = 'display' ) { | ||
| 338 | if ( ! isset( $this->message ) ) { | ||
| 339 | $message = ''; | ||
| 340 | |||
| 341 | if ( ! empty( $this->get_setting( 'customMessage' ) ) ) { | ||
| 342 | $message = $this->get_setting( 'customMessage' ); | ||
| 343 | } elseif ( ! empty( $this->post->post_content ) ) { | ||
| 344 | $message = 'display' === $context | ||
| 345 | ? \get_the_content( null, false, $this->id ) | ||
| 346 | : $this->post->post_content; | ||
| 347 | } | ||
| 348 | |||
| 349 | $this->message = $message; | ||
| 350 | } | ||
| 351 | |||
| 352 | return sanitize_post_field( 'post_content', $this->message, $this->id, $context ); | ||
| 353 | } | ||
| 354 | |||
| 355 | /** | ||
| 356 | * Whether to show excerpts for posts that are restricted. | ||
| 357 | * | ||
| 358 | * @return bool | ||
| 359 | */ | ||
| 360 | public function show_excerpts() { | ||
| 361 | return (bool) $this->get_setting( 'showExcerpts' ); | ||
| 362 | } | ||
| 363 | |||
| 364 | /** | ||
| 365 | * Check if this uses the redirect method. | ||
| 366 | * | ||
| 367 | * @return bool | ||
| 368 | */ | ||
| 369 | public function uses_redirect_method() { | ||
| 370 | return 'redirect' === $this->get_setting( 'protectionMethod' ); | ||
| 371 | } | ||
| 372 | |||
| 373 | /** | ||
| 374 | * Check if this uses the replace method. | ||
| 375 | * | ||
| 376 | * @return bool | ||
| 377 | */ | ||
| 378 | public function uses_replace_method() { | ||
| 379 | return 'replace' === $this->get_setting( 'protectionMethod' ); | ||
| 380 | } | ||
| 381 | |||
| 382 | /** | ||
| 383 | * Get edit link. | ||
| 384 | * | ||
| 385 | * @return string | ||
| 386 | */ | ||
| 387 | public function get_edit_link() { | ||
| 388 | if ( current_user_can( 'edit_post', $this->id ) ) { | ||
| 389 | return admin_url( 'options-general.php?page=content-control-settings&view=restrictions&edit=' . $this->id ); | ||
| 390 | } | ||
| 391 | |||
| 392 | return ''; | ||
| 393 | } | ||
| 394 | |||
| 395 | /** | ||
| 396 | * Convert this restriction to an array. | ||
| 397 | * | ||
| 398 | * @return array<string,mixed> | ||
| 399 | */ | ||
| 400 | public function to_array() { | ||
| 401 | $settings = $this->get_settings(); | ||
| 402 | |||
| 403 | return array_merge( [ | ||
| 404 | 'id' => $this->id, | ||
| 405 | 'slug' => $this->slug, | ||
| 406 | 'title' => $this->title, | ||
| 407 | 'description' => $this->get_description(), | ||
| 408 | 'message' => $this->get_message(), | ||
| 409 | 'status' => $this->status, | ||
| 410 | 'priority' => $this->priority, | ||
| 411 | ], $settings ); | ||
| 412 | } | ||
| 413 | |||
| 414 | /** | ||
| 415 | * Convert this restriction to a v1 array. | ||
| 416 | * | ||
| 417 | * @return array<string,mixed> | ||
| 418 | */ | ||
| 419 | public function to_v1_array() { | ||
| 420 | return [ | ||
| 421 | 'id' => $this->id, | ||
| 422 | 'title' => $this->title, | ||
| 423 | 'who' => $this->get_setting( 'userStatus' ), | ||
| 424 | 'roles' => $this->get_setting( 'userRoles' ), | ||
| 425 | 'protection_method' => $this->get_setting( 'protectionMethod' ), | ||
| 426 | 'show_excerpts' => $this->get_setting( 'showExcerpts' ), | ||
| 427 | 'override_default_message' => $this->get_setting( 'overrideMessage' ), | ||
| 428 | 'custom_message' => $this->get_setting( 'customMessage' ), | ||
| 429 | 'redirect_type' => $this->get_setting( 'redirectType' ), | ||
| 430 | 'redirect_url' => $this->get_setting( 'redirectUrl' ), | ||
| 431 | 'conditions' => $this->get_setting( 'conditions' ), | ||
| 432 | ]; | ||
| 433 | } | ||
| 434 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Rule engine group model. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | * @subpackage Models | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Models\RuleEngine; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Handler for condition groups. | ||
| 13 | * | ||
| 14 | * @package ContentControl | ||
| 15 | */ | ||
| 16 | class Group extends Item { | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Group id. | ||
| 20 | * | ||
| 21 | * @var string | ||
| 22 | */ | ||
| 23 | public $id; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Group label. | ||
| 27 | * | ||
| 28 | * @var string | ||
| 29 | */ | ||
| 30 | public $label; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Group query. | ||
| 34 | * | ||
| 35 | * @var Query | ||
| 36 | */ | ||
| 37 | public $query; | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Build a group. | ||
| 41 | * | ||
| 42 | * @param array{id:string,label:string,query:array<mixed>} $group Group data. | ||
| 43 | */ | ||
| 44 | public function __construct( $group ) { | ||
| 45 | $group = wp_parse_args( $group, [ | ||
| 46 | 'id' => '', | ||
| 47 | 'label' => '', | ||
| 48 | 'query' => [], | ||
| 49 | ]); | ||
| 50 | |||
| 51 | $this->id = $group['id']; | ||
| 52 | $this->label = $group['label']; | ||
| 53 | $this->query = new Query( $group['query'] ); | ||
| 54 | } | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Check if this group has JS based rules. | ||
| 58 | * | ||
| 59 | * @return bool | ||
| 60 | */ | ||
| 61 | public function has_js_rules() { | ||
| 62 | return $this->query->has_js_rules(); | ||
| 63 | } | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Check this groups rules. | ||
| 67 | * | ||
| 68 | * @return bool | ||
| 69 | */ | ||
| 70 | public function check_rules() { | ||
| 71 | return $this->query->check_rules(); | ||
| 72 | } | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Check this groups rules. | ||
| 76 | * | ||
| 77 | * @return array<bool|null|array<bool|null>> | ||
| 78 | */ | ||
| 79 | public function get_checks() { | ||
| 80 | return $this->query->get_checks(); | ||
| 81 | } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Return the rule check as an array of information. | ||
| 85 | * | ||
| 86 | * Useful for debugging. | ||
| 87 | * | ||
| 88 | * @return array<mixed> | ||
| 89 | */ | ||
| 90 | public function get_check_info() { | ||
| 91 | return $this->query->get_check_info(); | ||
| 92 | } | ||
| 93 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Rule engine item model. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | * @subpackage Models | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Models\RuleEngine; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Handler for condition items. | ||
| 13 | * | ||
| 14 | * @package ContentControl | ||
| 15 | */ | ||
| 16 | abstract class Item { | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Item id. | ||
| 20 | * | ||
| 21 | * @var string | ||
| 22 | */ | ||
| 23 | public $id; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Return the checks as an array of information. | ||
| 27 | * | ||
| 28 | * Useful for debugging. | ||
| 29 | * | ||
| 30 | * @return array<mixed> | ||
| 31 | */ | ||
| 32 | abstract public function get_check_info(); | ||
| 33 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Rule engine query model. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | * @subpackage Models | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Models\RuleEngine; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Handler for condition queries. | ||
| 13 | * | ||
| 14 | * @package ContentControl | ||
| 15 | */ | ||
| 16 | class Query { | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Query logical comparison operator. | ||
| 20 | * | ||
| 21 | * @var string `and` | `or` | ||
| 22 | */ | ||
| 23 | public $logical_operator; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Query items. | ||
| 27 | * | ||
| 28 | * @var Item[] | ||
| 29 | */ | ||
| 30 | public $items; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Build a query. | ||
| 34 | * | ||
| 35 | * @param array{logicalOperator:string,items:array<mixed>} $query Query data. | ||
| 36 | */ | ||
| 37 | public function __construct( $query ) { | ||
| 38 | $query = wp_parse_args( $query, [ | ||
| 39 | 'logicalOperator' => 'and', | ||
| 40 | 'items' => [], | ||
| 41 | ]); | ||
| 42 | |||
| 43 | $this->logical_operator = $query['logicalOperator']; | ||
| 44 | $this->items = []; | ||
| 45 | |||
| 46 | foreach ( $query['items'] as $item ) { | ||
| 47 | $is_group = ( isset( $item['type'] ) && 'group' === $item['type'] ) | ||
| 48 | || isset( $item['query'] ); | ||
| 49 | |||
| 50 | $this->items[] = $is_group ? new Group( $item ) : new Rule( $item ); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Check if this query has any rules. | ||
| 56 | * | ||
| 57 | * @return bool | ||
| 58 | */ | ||
| 59 | public function has_rules() { | ||
| 60 | return ! empty( $this->items ); | ||
| 61 | } | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Check if this query has JS based rules. | ||
| 65 | * | ||
| 66 | * @return bool | ||
| 67 | */ | ||
| 68 | public function has_js_rules() { | ||
| 69 | foreach ( $this->items as $item ) { | ||
| 70 | if ( $item instanceof Rule ) { | ||
| 71 | if ( $item->is_js_rule() ) { | ||
| 72 | return true; | ||
| 73 | } | ||
| 74 | } elseif ( $item instanceof Group ) { | ||
| 75 | if ( $item->has_js_rules() ) { | ||
| 76 | return true; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | return false; | ||
| 82 | } | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Check rules in a recursive nested pattern. | ||
| 86 | * | ||
| 87 | * @return bool | ||
| 88 | */ | ||
| 89 | public function check_rules() { | ||
| 90 | $checks = []; | ||
| 91 | |||
| 92 | if ( empty( $this->items ) ) { | ||
| 93 | return true; | ||
| 94 | } | ||
| 95 | |||
| 96 | foreach ( $this->items as $item ) { | ||
| 97 | // Missing rules should result in restricted content. | ||
| 98 | $result = false; | ||
| 99 | |||
| 100 | if ( $item instanceof Rule ) { | ||
| 101 | $result = $item->check_rule(); | ||
| 102 | } elseif ( $item instanceof Group ) { | ||
| 103 | $result = $item->check_rules(); | ||
| 104 | } | ||
| 105 | |||
| 106 | $checks[] = $result; | ||
| 107 | |||
| 108 | // Bail as early as we can. | ||
| 109 | if ( | ||
| 110 | // If we have a true result and are using `or`. | ||
| 111 | ( true === $result && 'or' === $this->logical_operator ) || | ||
| 112 | // If we have a false result and are using `and`. | ||
| 113 | ( false === $result && 'and' === $this->logical_operator ) | ||
| 114 | ) { | ||
| 115 | break; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | /* | ||
| 120 | * This method ignores null values (JS conditions), | ||
| 121 | * if changed, null needs to be accounted for. | ||
| 122 | */ | ||
| 123 | if ( 'or' === $this->logical_operator ) { | ||
| 124 | // If any values are true or null, return true. | ||
| 125 | return in_array( true, $checks, true ); | ||
| 126 | } else { | ||
| 127 | // If any values are false, return false. | ||
| 128 | return ! in_array( false, $checks, true ); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Return the checks as an array. | ||
| 134 | * | ||
| 135 | * Useful for debugging or passing to JS. | ||
| 136 | * | ||
| 137 | * @return array<bool|null|array<bool|null>> | ||
| 138 | */ | ||
| 139 | public function get_checks() { | ||
| 140 | $checks = []; | ||
| 141 | |||
| 142 | foreach ( $this->items as $item ) { | ||
| 143 | if ( $item instanceof Rule ) { | ||
| 144 | $checks[] = $item->get_check(); | ||
| 145 | } elseif ( $item instanceof Group ) { | ||
| 146 | $checks[] = $item->get_checks(); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | return $checks; | ||
| 151 | } | ||
| 152 | |||
| 153 | /** | ||
| 154 | * Return the checks as an array of information. | ||
| 155 | * | ||
| 156 | * Useful for debugging. | ||
| 157 | * | ||
| 158 | * @return array<mixed> | ||
| 159 | */ | ||
| 160 | public function get_check_info() { | ||
| 161 | $checks = []; | ||
| 162 | |||
| 163 | foreach ( $this->items as $key => $item ) { | ||
| 164 | $checks[ $key ] = $item->get_check_info(); | ||
| 165 | } | ||
| 166 | |||
| 167 | return $checks; | ||
| 168 | } | ||
| 169 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Rule engine rule model. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | * @subpackage Models | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Models\RuleEngine; | ||
| 10 | |||
| 11 | use function ContentControl\plugin; | ||
| 12 | use function ContentControl\Rules\current_rule; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Handler for condition rules. | ||
| 16 | * | ||
| 17 | * @package ContentControl | ||
| 18 | */ | ||
| 19 | class Rule extends Item { | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Unique Hash ID. | ||
| 23 | * | ||
| 24 | * @var string | ||
| 25 | */ | ||
| 26 | public $id; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Rule name. | ||
| 30 | * | ||
| 31 | * @var string | ||
| 32 | */ | ||
| 33 | public $name; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Rule options. | ||
| 37 | * | ||
| 38 | * @var array<string,mixed> | ||
| 39 | */ | ||
| 40 | public $options; | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Rule not operand. | ||
| 44 | * | ||
| 45 | * @var boolean | ||
| 46 | */ | ||
| 47 | public $not_operand; | ||
| 48 | |||
| 49 | /** | ||
| 50 | * Rule extras. | ||
| 51 | * | ||
| 52 | * Such as post type or taxnomy like meta. | ||
| 53 | * | ||
| 54 | * @var array<string,mixed> | ||
| 55 | */ | ||
| 56 | public $extras = []; | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Rule is frontend only. | ||
| 60 | * | ||
| 61 | * @var boolean | ||
| 62 | */ | ||
| 63 | public $frontend_only = false; | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Rule definition. | ||
| 67 | * | ||
| 68 | * @var array<string,mixed>|null | ||
| 69 | */ | ||
| 70 | public $definition; | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Rule is deprecated. | ||
| 74 | * | ||
| 75 | * @var boolean | ||
| 76 | */ | ||
| 77 | public $deprecated = false; | ||
| 78 | |||
| 79 | /** | ||
| 80 | * Build a rule. | ||
| 81 | * | ||
| 82 | * @param array{id:string,name:string,notOperand:bool,options:array<string,mixed>,extras:array<string,mixed>} $rule Rule data. | ||
| 83 | */ | ||
| 84 | public function __construct( $rule ) { | ||
| 85 | $rule = wp_parse_args( $rule, [ | ||
| 86 | 'id' => '', | ||
| 87 | 'name' => '', | ||
| 88 | 'notOperand' => false, | ||
| 89 | 'options' => [], | ||
| 90 | 'extras' => [], | ||
| 91 | ]); | ||
| 92 | |||
| 93 | if ( isset( $rule['deprecated'] ) ) { | ||
| 94 | $this->deprecated = $rule['deprecated']; | ||
| 95 | } | ||
| 96 | |||
| 97 | $name = $rule['name']; | ||
| 98 | |||
| 99 | $this->definition = plugin( 'rules' )->get_rule( $name ); | ||
| 100 | |||
| 101 | if ( ! $this->definition ) { | ||
| 102 | /* translators: 1. Rule name. */ | ||
| 103 | plugin( 'logging' )->log_unique( 'ERROR: ' . sprintf( __( 'Rule `%s` not found.', 'content-control' ), $name ) ); | ||
| 104 | } | ||
| 105 | |||
| 106 | $extras = isset( $this->definition['extras'] ) ? $this->definition['extras'] : []; | ||
| 107 | |||
| 108 | $this->id = $rule['id']; | ||
| 109 | $this->name = $name; | ||
| 110 | $this->not_operand = $rule['notOperand']; | ||
| 111 | $this->frontend_only = isset( $this->definition['frontend'] ) ? $this->definition['frontend'] : false; | ||
| 112 | $this->options = $this->parse_options( $rule['options'] ); | ||
| 113 | $this->extras = array_merge( $extras, $rule['extras'] ); | ||
| 114 | } | ||
| 115 | |||
| 116 | /** | ||
| 117 | * Parse rule options based on rule definitions. | ||
| 118 | * | ||
| 119 | * @param array<string,mixed> $options Array of rule opions. | ||
| 120 | * @return array<string,mixed> | ||
| 121 | */ | ||
| 122 | public function parse_options( $options = [] ) { | ||
| 123 | return $options; | ||
| 124 | } | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Check the results of this rule. | ||
| 128 | * | ||
| 129 | * @return bool | ||
| 130 | */ | ||
| 131 | public function check_rule() { | ||
| 132 | if ( $this->is_js_rule() ) { | ||
| 133 | return true; | ||
| 134 | } | ||
| 135 | |||
| 136 | $check = $this->run_check(); | ||
| 137 | |||
| 138 | return $this->not_operand ? ! $check : $check; | ||
| 139 | } | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Check the results of this rule. | ||
| 143 | * | ||
| 144 | * @return bool True if rule passes, false if not. | ||
| 145 | */ | ||
| 146 | private function run_check() { | ||
| 147 | $callback = isset( $this->definition['callback'] ) ? $this->definition['callback'] : null; | ||
| 148 | |||
| 149 | if ( ! $callback ) { | ||
| 150 | /* translators: 1. Rule name. */ | ||
| 151 | plugin( 'logging' )->log_unique( 'ERROR: ' . esc_html( sprintf( __( 'Rule `%s` has no callback.', 'content-control' ), $this->name ) ) ); | ||
| 152 | return false; | ||
| 153 | } | ||
| 154 | |||
| 155 | if ( ! is_callable( $callback ) ) { | ||
| 156 | /* translators: 1. Rule name. 2. Callback name. */ | ||
| 157 | plugin( 'logging' )->log_unique( 'ERROR: ' . esc_html( sprintf( __( 'Rule `%1$s` callback is not callable (%2$s).', 'content-control' ), $this->name, $callback ) ) ); | ||
| 158 | return false; | ||
| 159 | } | ||
| 160 | |||
| 161 | // Set global current rule so it can be easily accessed. | ||
| 162 | current_rule( $this ); | ||
| 163 | |||
| 164 | if ( $this->deprecated ) { | ||
| 165 | $settings = [ | ||
| 166 | 'target' => $this->name, | ||
| 167 | 'settings' => $this->options, | ||
| 168 | ]; | ||
| 169 | |||
| 170 | // Old rules had the settings passed as the first argument. | ||
| 171 | $check = call_user_func( $callback, $settings ); | ||
| 172 | } else { | ||
| 173 | /** | ||
| 174 | * All rule options can be accessed via the global. | ||
| 175 | * | ||
| 176 | * @see \ContentControl\Rules\current_rule() | ||
| 177 | */ | ||
| 178 | $check = call_user_func( $callback ); | ||
| 179 | } | ||
| 180 | |||
| 181 | // Clear global current rule. | ||
| 182 | current_rule( null ); | ||
| 183 | |||
| 184 | return $check; | ||
| 185 | } | ||
| 186 | |||
| 187 | /** | ||
| 188 | * Check if this rule's callback is based in JS rather than PHP. | ||
| 189 | * | ||
| 190 | * @return bool | ||
| 191 | */ | ||
| 192 | public function is_js_rule() { | ||
| 193 | return $this->frontend_only; | ||
| 194 | } | ||
| 195 | |||
| 196 | /** | ||
| 197 | * Return the rule check as boolean or null if the rule is JS based. | ||
| 198 | * | ||
| 199 | * @return bool|null | ||
| 200 | */ | ||
| 201 | public function get_check() { | ||
| 202 | if ( $this->is_js_rule() ) { | ||
| 203 | return null; | ||
| 204 | } | ||
| 205 | |||
| 206 | return $this->run_check(); | ||
| 207 | } | ||
| 208 | |||
| 209 | /** | ||
| 210 | * Return the rule check as an array of information. | ||
| 211 | * | ||
| 212 | * Useful for debugging. | ||
| 213 | * | ||
| 214 | * @return array<string,mixed>|null | ||
| 215 | */ | ||
| 216 | public function get_check_info() { | ||
| 217 | if ( $this->is_js_rule() ) { | ||
| 218 | return null; | ||
| 219 | } | ||
| 220 | |||
| 221 | return [ | ||
| 222 | 'result' => $this->run_check(), | ||
| 223 | 'id' => $this->id, | ||
| 224 | 'rule' => $this->name, | ||
| 225 | 'not' => $this->not_operand, | ||
| 226 | 'args' => $this->options, | ||
| 227 | 'def' => $this->definition, | ||
| 228 | ]; | ||
| 229 | } | ||
| 230 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Rule engine set model. | ||
| 4 | * | ||
| 5 | * @package ContentControl | ||
| 6 | * @subpackage Models | ||
| 7 | */ | ||
| 8 | |||
| 9 | namespace ContentControl\Models\RuleEngine; | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Handler for condition sets. | ||
| 13 | * | ||
| 14 | * @package ContentControl | ||
| 15 | */ | ||
| 16 | class Set { | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Set id. | ||
| 20 | * | ||
| 21 | * @var string | ||
| 22 | */ | ||
| 23 | public $id; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Set label. | ||
| 27 | * | ||
| 28 | * @var string | ||
| 29 | */ | ||
| 30 | public $label; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Set query. | ||
| 34 | * | ||
| 35 | * @var Query | ||
| 36 | */ | ||
| 37 | public $query; | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Build a set. | ||
| 41 | * | ||
| 42 | * @param array{id:string,label:string,query:array<mixed>} $set Set data. | ||
| 43 | */ | ||
| 44 | public function __construct( $set ) { | ||
| 45 | $set = wp_parse_args( $set, [ | ||
| 46 | 'id' => '', | ||
| 47 | 'label' => '', | ||
| 48 | 'query' => [], | ||
| 49 | ]); | ||
| 50 | |||
| 51 | $this->id = $set['id']; | ||
| 52 | $this->label = $set['label']; | ||
| 53 | $this->query = new Query( $set['query'] ); | ||
| 54 | } | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Check if this set has JS based rules. | ||
| 58 | * | ||
| 59 | * @return bool | ||
| 60 | */ | ||
| 61 | public function has_js_rules() { | ||
| 62 | return $this->query->has_js_rules(); | ||
| 63 | } | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Check this sets rules. | ||
| 67 | * | ||
| 68 | * @return bool | ||
| 69 | */ | ||
| 70 | public function check_rules() { | ||
| 71 | return $this->query->check_rules(); | ||
| 72 | } | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Get the check array for further post processing. | ||
| 76 | * | ||
| 77 | * @return array<bool|null|array<bool|null>> | ||
| 78 | */ | ||
| 79 | public function get_checks() { | ||
| 80 | return $this->query->get_checks(); | ||
| 81 | } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Return the checks as an array of information. | ||
| 85 | * | ||
| 86 | * Useful for debugging. | ||
| 87 | * | ||
| 88 | * @return array<string,mixed> | ||
| 89 | */ | ||
| 90 | public function get_check_info() { | ||
| 91 | return $this->query->get_check_info(); | ||
| 92 | } | ||
| 93 | } |
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Includes the composer Autoloader used for packages and classes in the classes/ directory. | ||
| 4 | * | ||
| 5 | * @package ContentControl\Plugin | ||
| 6 | */ | ||
| 7 | |||
| 8 | namespace ContentControl\Plugin; | ||
| 9 | |||
| 10 | defined( 'ABSPATH' ) || exit; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * Autoloader class. | ||
| 14 | */ | ||
| 15 | class Autoloader { | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Static-only class. | ||
| 19 | */ | ||
| 20 | private function __construct() { | ||
| 21 | } | ||
| 22 | |||
| 23 | /** | ||
| 24 | * Require the autoloader and return the result. | ||
| 25 | * | ||
| 26 | * If the autoloader is not present, let's log the failure and display a nice admin notice. | ||
| 27 | * | ||
| 28 | * @param string $name Plugin name for error messaging. | ||
| 29 | * @param string $path Path to the plugin. | ||
| 30 | * | ||
| 31 | * @return boolean | ||
| 32 | */ | ||
| 33 | public static function init( $name = '', $path = '' ) { | ||
| 34 | $autoloader = $path . '/vendor/autoload.php'; | ||
| 35 | |||
| 36 | if ( ! \is_readable( $autoloader ) ) { | ||
| 37 | self::missing_autoloader( $name ); | ||
| 38 | |||
| 39 | return false; | ||
| 40 | } | ||
| 41 | |||
| 42 | require_once $autoloader; | ||
| 43 | |||
| 44 | return true; | ||
| 45 | } | ||
| 46 | |||
| 47 | /** | ||
| 48 | * If the autoloader is missing, add an admin notice. | ||
| 49 | * | ||
| 50 | * @param string $plugin_name Plugin name for error messaging. | ||
| 51 | * | ||
| 52 | * @return void | ||
| 53 | */ | ||
| 54 | protected static function missing_autoloader( $plugin_name = '' ) { | ||
| 55 | /* translators: 1. Plugin name */ | ||
| 56 | $text = __( 'Your installation of %1$s is incomplete. If you installed %1$s from GitHub, please refer to this document to set up your development environment.', 'content-control' ); | ||
| 57 | |||
| 58 | $message = sprintf( $text, $plugin_name ); | ||
| 59 | |||
| 60 | if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { | ||
| 61 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | ||
| 62 | error_log( | ||
| 63 | esc_html( $message ) | ||
| 64 | ); | ||
| 65 | } | ||
| 66 | |||
| 67 | add_action( | ||
| 68 | 'admin_notices', | ||
| 69 | function () use ( $message ) { | ||
| 70 | ?> | ||
| 71 | <div class="notice notice-error"> | ||
| 72 | <p><?php echo esc_html( $message ); ?></p> | ||
| 73 | </div> | ||
| 74 | <?php | ||
| 75 | } | ||
| 76 | ); | ||
| 77 | } | ||
| 78 | } |
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Plugin installer. | ||
| 4 | * | ||
| 5 | * @copyright (c) 2021, Code Atlantic LLC. | ||
| 6 | * | ||
| 7 | * @package ContentControl\Plugin | ||
| 8 | */ | ||
| 9 | |||
| 10 | namespace ContentControl\Plugin; | ||
| 11 | |||
| 12 | use function ContentControl\plugin; | ||
| 13 | |||
| 14 | defined( 'ABSPATH' ) || exit; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Class Install | ||
| 18 | * | ||
| 19 | * @since 1.0.0 | ||
| 20 | */ | ||
| 21 | class Install { | ||
| 22 | |||
| 23 | /** | ||
| 24 | * Activation wrapper. | ||
| 25 | * | ||
| 26 | * @param bool $network_wide Weather to activate network wide. | ||
| 27 | * | ||
| 28 | * @return void | ||
| 29 | */ | ||
| 30 | public static function activate_plugin( $network_wide ) { | ||
| 31 | self::do_multisite( $network_wide, [ __CLASS__, 'activate_site' ] ); | ||
| 32 | } | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Deactivation wrapper. | ||
| 36 | * | ||
| 37 | * @param bool $network_wide Weather to deactivate network wide. | ||
| 38 | * | ||
| 39 | * @return void | ||
| 40 | */ | ||
| 41 | public static function deactivate_plugin( $network_wide ) { | ||
| 42 | self::do_multisite( $network_wide, [ __CLASS__, 'deactivate_site' ] ); | ||
| 43 | } | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Uninstall the plugin. | ||
| 47 | * | ||
| 48 | * @return void | ||
| 49 | */ | ||
| 50 | public static function uninstall_plugin() { | ||
| 51 | self::do_multisite( true, [ __CLASS__, 'uninstall_site' ] ); | ||
| 52 | } | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Handle single & multisite processes. | ||
| 56 | * | ||
| 57 | * @param bool $network_wide Weather to do it network wide. | ||
| 58 | * @param callable $method Callable method for each site. | ||
| 59 | * @param array<string,mixed> $args Array of extra args. | ||
| 60 | * | ||
| 61 | * @return void | ||
| 62 | */ | ||
| 63 | private static function do_multisite( $network_wide, $method, $args = [] ) { | ||
| 64 | global $wpdb; | ||
| 65 | |||
| 66 | if ( is_multisite() && $network_wide ) { | ||
| 67 | $activated = get_site_option( 'content_control_activated', [] ); | ||
| 68 | |||
| 69 | /* phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery */ | ||
| 70 | $blog_ids = $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs}" ); | ||
| 71 | |||
| 72 | // Try to reduce the chances of a timeout with a large number of sites. | ||
| 73 | if ( \count( $blog_ids ) > 2 ) { | ||
| 74 | ignore_user_abort( true ); | ||
| 75 | |||
| 76 | if ( ! \ContentControl\is_func_disabled( 'set_time_limit' ) ) { | ||
| 77 | /* phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged */ | ||
| 78 | @set_time_limit( 0 ); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | foreach ( $blog_ids as $blog_id ) { | ||
| 83 | switch_to_blog( $blog_id ); | ||
| 84 | call_user_func_array( $method, [ $args ] ); | ||
| 85 | |||
| 86 | $activated[] = $blog_id; | ||
| 87 | |||
| 88 | restore_current_blog(); | ||
| 89 | } | ||
| 90 | |||
| 91 | update_site_option( 'content_control_activated', $activated ); | ||
| 92 | } else { | ||
| 93 | call_user_func_array( $method, [ $args ] ); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Activate on single site. | ||
| 99 | * | ||
| 100 | * @return void | ||
| 101 | */ | ||
| 102 | public static function activate_site() { | ||
| 103 | // Add a temporary option that will fire a hookable action on next load. | ||
| 104 | \set_transient( '_content_control_installed', true, 3600 ); | ||
| 105 | |||
| 106 | $version = plugin()->get( 'version' ); | ||
| 107 | |||
| 108 | // Add version info. | ||
| 109 | \add_option( 'content_control_version', [ | ||
| 110 | 'version' => $version, | ||
| 111 | 'upgraded_from' => null, | ||
| 112 | 'initial_version' => $version, | ||
| 113 | 'installed_on' => gmdate( 'Y-m-d H:i:s' ), | ||
| 114 | ] ); | ||
| 115 | |||
| 116 | // Add data versions if missing. | ||
| 117 | \add_option( 'content_control_data_versioning', \ContentControl\current_data_versions() ); | ||
| 118 | } | ||
| 119 | |||
| 120 | /** | ||
| 121 | * Deactivate on single site. | ||
| 122 | * | ||
| 123 | * @return void | ||
| 124 | */ | ||
| 125 | public static function deactivate_site() { | ||
| 126 | } | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Uninstall single site. | ||
| 130 | * | ||
| 131 | * @return void | ||
| 132 | */ | ||
| 133 | public static function uninstall_site() { | ||
| 134 | } | ||
| 135 | } |
This diff is collapsed.
Click to expand it.
| 1 | <?php | ||
| 2 | /** | ||
| 3 | * Logging class. | ||
| 4 | * | ||
| 5 | * @package ContentControl\Plugin | ||
| 6 | */ | ||
| 7 | |||
| 8 | namespace ContentControl\Plugin; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * Logging class. | ||
| 12 | */ | ||
| 13 | class Logging { | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Log file prefix. | ||
| 17 | */ | ||
| 18 | const LOG_FILE_PREFIX = 'content-control-'; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Whether the log file is writable. | ||
| 22 | * | ||
| 23 | * @var bool|null | ||
| 24 | */ | ||
| 25 | private $is_writable; | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Log file name. | ||
| 29 | * | ||
| 30 | * @var string | ||
| 31 | */ | ||
| 32 | private $filename = ''; | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Log file path. | ||
| 36 | * | ||
| 37 | * @var string | ||
| 38 | */ | ||
| 39 | private $file = ''; | ||
| 40 | |||
| 41 | /** | ||
| 42 | * File system API. | ||
| 43 | * | ||
| 44 | * @var \WP_Filesystem_Base|null | ||
| 45 | */ | ||
| 46 | private $fs; | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Log file content. | ||
| 50 | * | ||
| 51 | * @var string|null | ||
| 52 | */ | ||
| 53 | private $content; | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Initialize logging. | ||
| 57 | */ | ||
| 58 | public function __construct() { | ||
| 59 | $this->init(); | ||
| 60 | |||
| 61 | $this->register_hooks(); | ||
| 62 | } | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Register hooks. | ||
| 66 | * | ||
| 67 | * @return void | ||
| 68 | */ | ||
| 69 | public function register_hooks() { | ||
| 70 | // On shutdown, save the log file. | ||
| 71 | add_action( 'shutdown', [ $this, 'save_logs' ] ); | ||
| 72 | } | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Gets the Uploads directory | ||
| 76 | * | ||
| 77 | * @return bool|array{path: string, url: string, subdir: string, basedir: string, baseurl: string, error: string|false} An associated array with baseurl and basedir or false on failure | ||
| 78 | */ | ||
| 79 | public function get_upload_dir() { | ||
| 80 | // Used if you only need to fetch data, not create missing folders. | ||
| 81 | $wp_upload_dir = wp_get_upload_dir(); | ||
| 82 | |||
| 83 | // phpcs:ignore Squiz.PHP.CommentedOutCode.Found | ||
| 84 | // $wp_upload_dir = wp_upload_dir(); // Disable this on IS_WPCOM if used. | ||
| 85 | |||
| 86 | if ( isset( $wp_upload_dir['error'] ) && false !== $wp_upload_dir['error'] ) { | ||
| 87 | return false; | ||
| 88 | } else { | ||
| 89 | return $wp_upload_dir; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Gets the uploads directory URL | ||
| 95 | * | ||
| 96 | * @param string $path A path to append to end of upload directory URL. | ||
| 97 | * @return bool|string The uploads directory URL or false on failure | ||
| 98 | */ | ||
| 99 | public function get_upload_dir_url( $path = '' ) { | ||
| 100 | $upload_dir = $this->get_upload_dir(); | ||
| 101 | if ( false !== $upload_dir && isset( $upload_dir['baseurl'] ) ) { | ||
| 102 | $url = preg_replace( '/^https?:/', '', $upload_dir['baseurl'] ); | ||
| 103 | if ( null === $url ) { | ||
| 104 | return false; | ||
| 105 | } | ||
| 106 | if ( ! empty( $path ) ) { | ||
| 107 | $url = trailingslashit( $url ) . $path; | ||
| 108 | } | ||
| 109 | return $url; | ||
| 110 | } else { | ||
| 111 | return false; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | /** | ||
| 116 | * Chek if logging is enabled. | ||
| 117 | * | ||
| 118 | * @return bool | ||
| 119 | */ | ||
| 120 | public function enabled() { | ||
| 121 | $disabled = defined( '\CONTENT_CONTROL_DISABLE_LOGGING' ) && true === \CONTENT_CONTROL_DISABLE_LOGGING; | ||
| 122 | |||
| 123 | return ! $disabled && $this->is_writable(); | ||
| 124 | } | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Get working WP Filesystem instance | ||
| 128 | * | ||
| 129 | * @return \WP_Filesystem_Base|false | ||
| 130 | */ | ||
| 131 | public function fs() { | ||
| 132 | if ( isset( $this->fs ) ) { | ||
| 133 | return $this->fs; | ||
| 134 | } | ||
| 135 | |||
| 136 | global $wp_filesystem; | ||
| 137 | |||
| 138 | require_once ABSPATH . 'wp-admin/includes/file.php'; | ||
| 139 | |||
| 140 | // If for some reason the include doesn't work as expected just return false. | ||
| 141 | if ( ! function_exists( 'WP_Filesystem' ) ) { | ||
| 142 | return false; | ||
| 143 | } | ||
| 144 | |||
| 145 | $writable = WP_Filesystem( false, '', true ); | ||
| 146 | |||
| 147 | // We consider the directory as writable if it uses the direct transport, | ||
| 148 | // otherwise credentials would be needed. | ||
| 149 | $this->fs = ( $writable && 'direct' === $wp_filesystem->method ) ? $wp_filesystem : false; | ||
| 150 | |||
| 151 | return $this->fs; | ||
| 152 | } | ||
| 153 | |||
| 154 | /** | ||
| 155 | * Check if the log file is writable. | ||
| 156 | * | ||
| 157 | * @return boolean | ||
| 158 | */ | ||
| 159 | public function is_writable() { | ||
| 160 | if ( isset( $this->is_writable ) ) { | ||
| 161 | return $this->is_writable; | ||
| 162 | } | ||
| 163 | |||
| 164 | $file_system = $this->fs(); | ||
| 165 | |||
| 166 | if ( false === $file_system ) { | ||
| 167 | $this->is_writable = false; | ||
| 168 | return $this->is_writable; | ||
| 169 | } | ||
| 170 | |||
| 171 | $this->is_writable = 'direct' === $file_system->method; | ||
| 172 | |||
| 173 | $upload_dir = $this->get_upload_dir(); | ||
| 174 | |||
| 175 | if ( ! $file_system->is_writable( $upload_dir['basedir'] ) ) { | ||
| 176 | $this->is_writable = false; | ||
| 177 | } | ||
| 178 | |||
| 179 | return $this->is_writable; | ||
| 180 | } | ||
| 181 | |||
| 182 | /** | ||
| 183 | * Get things started | ||
| 184 | * | ||
| 185 | * @return void | ||
| 186 | */ | ||
| 187 | public function init() { | ||
| 188 | $upload_dir = $this->get_upload_dir(); | ||
| 189 | $file_system = $this->fs(); | ||
| 190 | |||
| 191 | if ( false === $upload_dir || false === $file_system ) { | ||
| 192 | return; | ||
| 193 | } | ||
| 194 | |||
| 195 | $file_token = \get_option( 'content_control_debug_log_token' ); | ||
| 196 | if ( false === $file_token ) { | ||
| 197 | $file_token = uniqid( (string) wp_rand(), true ); | ||
| 198 | \update_option( 'content_control_debug_log_token', $file_token ); | ||
| 199 | } | ||
| 200 | |||
| 201 | $this->filename = self::LOG_FILE_PREFIX . "debug-{$file_token}.log"; // ex. content-control-debug-5c2f6a9b9b5a3.log. | ||
| 202 | $this->file = trailingslashit( $upload_dir['basedir'] ) . $this->filename; | ||
| 203 | |||
| 204 | if ( ! $file_system->exists( $this->file ) ) { | ||
| 205 | $this->setup_new_log(); | ||
| 206 | } else { | ||
| 207 | $this->content = $this->get_file( $this->file ); | ||
| 208 | } | ||
| 209 | |||
| 210 | // Truncate long log files. | ||
| 211 | if ( $file_system->exists( $this->file ) && $file_system->size( $this->file ) >= 1048576 ) { | ||
| 212 | $this->truncate_log(); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | /** | ||
| 217 | * Get the log file path. | ||
| 218 | * | ||
| 219 | * @return string | ||
| 220 | */ | ||
| 221 | public function get_file_path() { | ||
| 222 | return $this->file; | ||
| 223 | } | ||
| 224 | |||
| 225 | /** | ||
| 226 | * Retrieves the url to the file | ||
| 227 | * | ||
| 228 | * @return string|bool The url to the file or false on failure | ||
| 229 | */ | ||
| 230 | public function get_file_url() { | ||
| 231 | if ( ! $this->enabled() ) { | ||
| 232 | return false; | ||
| 233 | } | ||
| 234 | |||
| 235 | return $this->get_upload_dir_url( $this->filename ); | ||
| 236 | } | ||
| 237 | |||
| 238 | /** | ||
| 239 | * Retrieve the log data | ||
| 240 | * | ||
| 241 | * @return false|string | ||
| 242 | */ | ||
| 243 | public function get_log() { | ||
| 244 | return $this->get_log_content(); | ||
| 245 | } | ||
| 246 | |||
| 247 | /** | ||
| 248 | * Delete the log file and token. | ||
| 249 | * | ||
| 250 | * @return void | ||
| 251 | */ | ||
| 252 | public function delete_logs() { | ||
| 253 | $file_system = $this->fs(); | ||
| 254 | |||
| 255 | if ( false === $file_system ) { | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | |||
| 259 | $file_system->delete( $this->file ); | ||
| 260 | \delete_option( 'content_control_debug_log_token' ); | ||
| 261 | } | ||
| 262 | |||
| 263 | /** | ||
| 264 | * Log message to file | ||
| 265 | * | ||
| 266 | * @param string $message The message to log. | ||
| 267 | * | ||
| 268 | * @return void | ||
| 269 | */ | ||
| 270 | public function log( $message = '' ) { | ||
| 271 | $this->write_to_log( wp_date( 'Y-n-d H:i:s' ) . ' - ' . $message ); | ||
| 272 | } | ||
| 273 | |||
| 274 | /** | ||
| 275 | * Log unique message to file. | ||
| 276 | * | ||
| 277 | * @param string $message The unique message to log. | ||
| 278 | * | ||
| 279 | * @return void | ||
| 280 | */ | ||
| 281 | public function log_unique( $message = '' ) { | ||
| 282 | $contents = $this->get_log_content(); | ||
| 283 | |||
| 284 | if ( strpos( $contents, $message ) !== false ) { | ||
| 285 | return; | ||
| 286 | } | ||
| 287 | |||
| 288 | $this->log( $message ); | ||
| 289 | } | ||
| 290 | |||
| 291 | /** | ||
| 292 | * Get the log file contents. | ||
| 293 | * | ||
| 294 | * @return false|string | ||
| 295 | */ | ||
| 296 | public function get_log_content() { | ||
| 297 | if ( ! isset( $this->content ) ) { | ||
| 298 | $this->content = $this->get_file(); | ||
| 299 | } | ||
| 300 | |||
| 301 | return $this->content; | ||
| 302 | } | ||
| 303 | |||
| 304 | /** | ||
| 305 | * Set the log file contents in memory. | ||
| 306 | * | ||
| 307 | * @param mixed $content The content to set. | ||
| 308 | * @param bool $save Whether to save the content to the file immediately. | ||
| 309 | * @return void | ||
| 310 | */ | ||
| 311 | private function set_log_content( $content, $save = false ) { | ||
| 312 | $this->content = $content; | ||
| 313 | |||
| 314 | if ( $save ) { | ||
| 315 | $this->save_logs(); | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 319 | /** | ||
| 320 | * Retrieve the contents of a file. | ||
| 321 | * | ||
| 322 | * @param string|boolean $file File to get contents of. | ||
| 323 | * | ||
| 324 | * @return false|string | ||
| 325 | */ | ||
| 326 | protected function get_file( $file = false ) { | ||
| 327 | $file = $file ? $file : $this->file; | ||
| 328 | |||
| 329 | $file_system = $this->fs(); | ||
| 330 | |||
| 331 | if ( false === $file_system || ! $this->enabled() ) { | ||
| 332 | return ''; | ||
| 333 | } | ||
| 334 | |||
| 335 | $content = ''; | ||
| 336 | |||
| 337 | if ( $file_system->exists( $file ) ) { | ||
| 338 | $content = $file_system->get_contents( $file ); | ||
| 339 | } | ||
| 340 | |||
| 341 | return $content; | ||
| 342 | } | ||
| 343 | |||
| 344 | /** | ||
| 345 | * Write the log message | ||
| 346 | * | ||
| 347 | * @param string $message The message to write. | ||
| 348 | * | ||
| 349 | * @return void | ||
| 350 | */ | ||
| 351 | protected function write_to_log( $message = '' ) { | ||
| 352 | if ( ! $this->enabled() ) { | ||
| 353 | return; | ||
| 354 | } | ||
| 355 | |||
| 356 | $contents = $this->get_log_content(); | ||
| 357 | |||
| 358 | // If it doesn't end with a new line, add one. \r\n length is 2. | ||
| 359 | if ( substr( $contents, -2 ) !== "\r\n" ) { | ||
| 360 | $contents .= "\r\n"; | ||
| 361 | } | ||
| 362 | |||
| 363 | $this->set_log_content( $contents . $message ); | ||
| 364 | } | ||
| 365 | |||
| 366 | /** | ||
| 367 | * Save the current contents to file. | ||
| 368 | * | ||
| 369 | * @return void | ||
| 370 | */ | ||
| 371 | public function save_logs() { | ||
| 372 | $file_system = $this->fs(); | ||
| 373 | |||
| 374 | if ( false === $file_system || ! $this->enabled() ) { | ||
| 375 | return; | ||
| 376 | } | ||
| 377 | |||
| 378 | $file_system->put_contents( $this->file, $this->content, FS_CHMOD_FILE ); | ||
| 379 | } | ||
| 380 | |||
| 381 | /** | ||
| 382 | * Get a line count. | ||
| 383 | * | ||
| 384 | * @return int | ||
| 385 | */ | ||
| 386 | public function count_lines() { | ||
| 387 | $file = $this->get_log_content(); | ||
| 388 | $lines = explode( "\r\n", $file ); | ||
| 389 | |||
| 390 | return count( $lines ); | ||
| 391 | } | ||
| 392 | |||
| 393 | /** | ||
| 394 | * Truncates a log file to maximum of 250 lines. | ||
| 395 | * | ||
| 396 | * @return void | ||
| 397 | */ | ||
| 398 | public function truncate_log() { | ||
| 399 | $content = $this->get_log_content(); | ||
| 400 | $lines = explode( "\r\n", $content ); | ||
| 401 | $lines = array_slice( $lines, 0, 250 ); // 50 is how many lines you want to keep | ||
| 402 | $truncated_content = implode( "\r\n", $lines ); | ||
| 403 | $this->set_log_content( $truncated_content, true ); | ||
| 404 | } | ||
| 405 | |||
| 406 | /** | ||
| 407 | * Set up a new log file. | ||
| 408 | * | ||
| 409 | * @return void | ||
| 410 | */ | ||
| 411 | public function setup_new_log() { | ||
| 412 | $this->set_log_content( "Content Control Debug Logs:\r\n" . wp_date( 'Y-n-d H:i:s' ) . " - Log file initialized\r\n", true ); | ||
| 413 | } | ||
| 414 | |||
| 415 | /** | ||
| 416 | * Delete the log file. | ||
| 417 | * | ||
| 418 | * @return void | ||
| 419 | */ | ||
| 420 | public function clear_log() { | ||
| 421 | $file_system = $this->fs(); | ||
| 422 | |||
| 423 | if ( false === $file_system ) { | ||
| 424 | return; | ||
| 425 | } | ||
| 426 | |||
| 427 | // Delete the file. | ||
| 428 | // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged | ||
| 429 | @$file_system->delete( $this->file ); | ||
| 430 | |||
| 431 | if ( $this->enabled() ) { | ||
| 432 | $this->setup_new_log(); | ||
| 433 | } | ||
| 434 | } | ||
| 435 | |||
| 436 | /** | ||
| 437 | * Log a deprecated notice. | ||
| 438 | * | ||
| 439 | * @param string $func_name Function name. | ||
| 440 | * @param string $version Versoin deprecated. | ||
| 441 | * @param string $replacement Replacement function (optional). | ||
| 442 | * | ||
| 443 | * @return void | ||
| 444 | */ | ||
| 445 | public function log_deprecated_notice( $func_name, $version, $replacement = null ) { | ||
| 446 | if ( ! is_null( $replacement ) ) { | ||
| 447 | $notice = sprintf( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.', $func_name, $version, $replacement ); | ||
| 448 | } else { | ||
| 449 | $notice = sprintf( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.', $func_name, $version ); | ||
| 450 | } | ||
| 451 | |||
| 452 | $this->log_unique( $notice ); | ||
| 453 | } | ||
| 454 | } |
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/index.php
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/pimple/pimple/src/Pimple/Container.php
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/pimple/pimple/src/Pimple/Psr11/Container.php
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/pimple/pimple/src/Pimple/ServiceIterator.php
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/pimple/pimple/src/Pimple/Tests/PimpleTest.php
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/psr/container/src/ContainerExceptionInterface.php
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/psr/container/src/ContainerInterface.php
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/psr/container/src/NotFoundExceptionInterface.php
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/Encryption.php
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/SecurityChecks.php
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/SiteAccess.php
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/SupportRole.php
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/SupportUser.php
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/index.php
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/loading.svg
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/lock.svg
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/src/_auth.scss
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/src/_buttons.scss
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/src/_global.scss
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/src/index.php
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/trustedlogin.css
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/trustedlogin.js
0 → 100644
This diff is collapsed.
Click to expand it.
wp-content/plugins/content-control/vendor-prefixed/trustedlogin/client/src/assets/trustedlogin.svg
0 → 100644
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
This diff is collapsed.
Click to expand it.
-
Please register or sign in to post a comment