diff --git a/admin/class-h5p-content-admin.php b/admin/class-h5p-content-admin.php index acb4c3b..888dec0 100644 --- a/admin/class-h5p-content-admin.php +++ b/admin/class-h5p-content-admin.php @@ -132,6 +132,47 @@ private function current_user_can_edit($content) { return get_current_user_id() === $author_id; } + /** + * Check whether current user has permission to share content. + * + * @since 1.15.5 + * @param array $content Content data. + * @return bool True, if current user is allowed to share content. Else false. + */ + private function current_user_can_share($content) { + if ( + empty(get_option('h5p_hub_is_enabled')) || + empty(get_option('h5p_h5p_site_uuid')) || + empty(get_option('h5p_hub_secret')) + ) { + return FALSE; // Hub is not ready to be shared with + } + + // If you can't share content, neither can you share others contents + if (!current_user_can('share_h5p_contents')) { + return FALSE; + } + if (current_user_can('share_others_h5p_contents')) { + return TRUE; + } + $author_id = (int)(is_array($content) ? + $content['user_id'] : + $content->user_id); + + return get_current_user_id() === $author_id; + } + + /** + * Determine whether content was shared successfully. + * + * @since 1.15.5 + * @return bool True if content was shared. + */ + private function is_content_shared() { + return (int)($this->content['shared']) !== + H5PContentStatus::STATUS_UNPUBLISHED; + } + /** * Permission check. Can the current user view the given content? * @@ -170,6 +211,68 @@ private function current_user_can_view_content_results($content) { return $this->current_user_can_edit($content); } + /** + * Check and update hub status. + * + * @since 1.15.5 + * @param array $content Content data. + */ + private function check_update_hub_status($content = []) { + if (empty($content)) { + return; // No content to check + } + + if (!$this->current_user_can_share($content)) { + return; // User is not allowed to update status + } + + if ( + empty($content['contentHubId']) || + (int)($content['synced']) === H5PContentHubSyncStatus::NOT_SYNCED + ) { + return; // Content needs syncing + } + + H5PContentSharing::update_hub_status($this->content); + H5P_Plugin_Admin::print_messages(); + } + + /** + * Display content. + * + * @since 1.15.5 + */ + public function display_content() { + // Access restriction + if ($this->current_user_can_view($this->content) == FALSE) { + H5P_Plugin_Admin::set_error( + __('You are not allowed to view this content.', $this->plugin_slug) + ); + H5P_Plugin_Admin::print_messages(); + return; + } + + // Admin preview of H5P content. + if (is_string($this->content)) { + H5P_Plugin_Admin::set_error($this->content); + H5P_Plugin_Admin::print_messages(); + } + else { + $plugin = H5P_Plugin::get_instance(); + $embed_code = $plugin->add_assets($this->content); + include_once('views/show-content.php'); + H5P_Plugin::get_instance()->add_settings(); + + // Log view + new H5P_Event('content', NULL, + $this->content['id'], + $this->content['title'], + $this->content['library']['name'], + $this->content['library']['majorVersion'] . '.' . + $this->content['library']['minorVersion']); + } + } + /** * Display a list of all h5p content. * @@ -233,31 +336,8 @@ public function display_contents_page() { return; case 'show': - // Access restriction - if ($this->current_user_can_view($this->content) == FALSE) { - H5P_Plugin_Admin::set_error(__('You are not allowed to view this content.', $this->plugin_slug)); - H5P_Plugin_Admin::print_messages(); - return; - } - - // Admin preview of H5P content. - if (is_string($this->content)) { - H5P_Plugin_Admin::set_error($this->content); - H5P_Plugin_Admin::print_messages(); - } - else { - $plugin = H5P_Plugin::get_instance(); - $embed_code = $plugin->add_assets($this->content); - include_once('views/show-content.php'); - H5P_Plugin::get_instance()->add_settings(); - - // Log view - new H5P_Event('content', NULL, - $this->content['id'], - $this->content['title'], - $this->content['library']['name'], - $this->content['library']['majorVersion'] . '.' . $this->content['library']['minorVersion']); - } + $this->check_update_hub_status($this->content); + $this->display_content(); return; case 'results': @@ -312,6 +392,45 @@ public function display_contents_page() { $this->content['library']['majorVersion'] . '.' . $this->content['library']['minorVersion']); } return; + + // Share content on the Hub + case 'share': { + $content_id = (int)filter_input( + INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT + ); + H5PContentSharing::display_share_content_form($content_id); + return; + } + + // Sync content on the Hub + case 'sync': { + $content_id = (int)filter_input(INPUT_GET, 'id', + FILTER_SANITIZE_NUMBER_INT + ); + H5PContentSharing::sync($content_id); + + $plugin = H5P_Plugin::get_instance(); + $this->content = $plugin->get_content((int)$content_id); + + $this->check_update_hub_status($this->content); + $this->display_content(); + return; + } + + // Share content on the Hub + case 'unshare': { + $content_id = (int)filter_input( + INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT + ); + H5PContentSharing::unshare($content_id); + + $plugin = H5P_Plugin::get_instance(); + $this->content = $plugin->get_content((int)$content_id); + + $this->check_update_hub_status($this->content); + $this->display_content(); + return; + } } print '

' . esc_html__('Unknown task.', $this->plugin_slug) . '

'; @@ -1030,6 +1149,9 @@ public function add_editor_assets($id = NULL) { // Add JavaScript settings $content_validator = $plugin->get_h5p_instance('contentvalidator'); + $content_search_url = H5PHubEndpoints::createURL(H5PHubEndpoints::CONTENT) . + '/search'; + $settings['editor'] = array( 'filesPath' => $plugin->get_h5p_url() . '/editor', 'fileIcon' => array( @@ -1044,7 +1166,12 @@ public function add_editor_assets($id = NULL) { 'assets' => $assets, 'deleteMessage' => __('Are you sure you wish to delete this content?', $this->plugin_slug), 'apiVersion' => H5PCore::$coreApi, - 'language' => $language + 'language' => $language, + 'hub' => array( + 'contentSearchUrl' => $content_search_url, + ), + 'enableContentHub' => !empty(get_option('h5p_h5p_site_uuid')) && + !empty(get_option('h5p_hub_secret')) ); if ($id !== NULL) { @@ -1183,4 +1310,42 @@ public function ajax_filter() { $editor->ajax->action(H5PEditorEndpoints::FILTER, $token, $libraryParameters); exit; } + + /* + * Handle filtering of parameters through AJAX. + * + * @since 1.15.5 + */ + public function ajax_h5p_content_hub_metadata_cache () { + $plugin = H5P_Plugin::get_instance(); + $plugin->get_h5p_instance('core'); + + // Check capability to register + if (!current_user_can('edit_h5p_contents')) { + H5PCore::ajaxError(__('You do not have permission to view the metadata for the content hub.', $plugin->get_plugin_slug()), 'NO_PERMISSION', 403); + wp_die(); + } + + $editor = $this->get_h5peditor_instance(); + $editor->ajax->action( + H5PEditorEndpoints::CONTENT_HUB_METADATA_CACHE, $plugin->get_language() + ); + exit; + } + + /** + * Get content from the H5P Hub. + * + * @since 1.15.5 + */ + public function ajax_h5p_get_content() { + $token = filter_input(INPUT_GET, 'token', FILTER_SANITIZE_STRING); + $hubid = filter_input(INPUT_GET, 'hubId'); + + $editor = $this->get_h5peditor_instance(); + $editor->ajax->action( + H5PEditorEndpoints::GET_HUB_CONTENT, $token, $hubid, NULL + ); + exit; + } } diff --git a/admin/class-h5p-content-hub-registration.php b/admin/class-h5p-content-hub-registration.php new file mode 100644 index 0000000..e2404c7 --- /dev/null +++ b/admin/class-h5p-content-hub-registration.php @@ -0,0 +1,136 @@ +, Joubel + * @license MIT + * @link http://joubel.com + * @copyright 2022 Joubel + */ + +/** + * H5P Content Hub Registration class. + * + * @package H5PContentHubRegistration + * @author Oliver Tacke , Joubel + */ +class H5PContentHubRegistration { + + /** + * Display form to register account on the H5P OER Hub. + * + * @since 1.15.5 + */ + public static function display_register_accout_form() { + $plugin = H5P_Plugin::get_instance(); + + // Check capability to register + if (!current_user_can('manage_h5p_content_hub_registration')) { + H5P_Plugin_Admin::set_error(__('You do not have permission to manage the registration with the content hub.', $plugin->get_plugin_slug())); + H5P_Plugin_Admin::print_messages(); + wp_die(); + } + + // Check token + if (!check_ajax_referer('h5p_content_hub_registration_form', FALSE, FALSE)) { + H5P_Plugin_Admin::set_error(__('Invalid security token.', $plugin->get_plugin_slug())); + H5P_Plugin_Admin::print_messages(); + wp_die(); + } + + // Let H5P core fetch account information from Hub + $core = $plugin->get_h5p_instance('core'); + try { + $accountInfo = $core->hubAccountInfo(); + } + catch (Exception $e) { + // Go back to H5P configuration, secret has to be removed manually + wp_safe_redirect(admin_url('options-general.php?page=h5p_settings')); + } + + /* + * Settings for H5P Hub client + * @link https://github.com/h5p/h5p-hub-client + */ + $settings = array( + 'H5PContentHubRegistration' => array( + 'registrationURL' => admin_url('admin-ajax.php?action=h5p_register_account&_wpnonce=' . wp_create_nonce( 'content_hub_registration' )), + 'accountSettingsUrl' => '', + 'token' => H5PCore::createToken('content_hub_registration'), + 'l10n' => $core->getLocalization(), + 'licenseAgreementTitle' => __('End User License Agreement (EULA)', $plugin->get_plugin_slug()), + 'licenseAgreementDescription' => __('Please read the following agreement before proceeding with the '), + 'licenseAgreementMainText' => 'TODO', // This is a TODO of the original implementation missing in other plugins, too + 'accountInfo' => $accountInfo, + ), + ); + + // Render to page + $plugin->print_settings($settings, 'H5POERHubRegistration'); + include_once('views/hub-registration.php'); + H5P_Plugin_Admin::add_style('h5p-css', 'h5p-php-library/styles/h5p.css'); + H5P_Plugin_Admin::add_script('h5p-hub-registration', 'h5p-php-library/js/h5p-hub-registration.js'); + H5P_Plugin_Admin::add_style('h5p-hub-registration-css', 'h5p-php-library/styles/h5p-hub-registration.css'); + H5P_Plugin_Admin::add_script('h5p-hub-registration-wp', 'admin/scripts/h5p-hub-registration.js'); + } + + /** + * Register with the H5P Content Hub. + * + * @since 1.15.5 + */ + public static function ajax_register_account() { + $plugin = H5P_Plugin::get_instance(); + + // Check capability to register + if (!current_user_can('manage_h5p_content_hub_registration')) { + H5PCore::ajaxError( + __('You do not have permission to register the site with the content hub.', + $plugin->get_plugin_slug()), 'NO_PERMISSION', 403 + ); + wp_die(); + } + + // Check token + if (!check_ajax_referer( 'content_hub_registration', FALSE, FALSE )) { + H5PCore::ajaxError( + __('Invalid security token.', $plugin->get_plugin_slug()) + ); + wp_die(); + } + + // Retrieve input from post message + $logo = isset($_FILES['logo']) ? $_FILES['logo'] : NULL; + $formdata = [ + 'name' => filter_input(INPUT_POST, 'name'), + 'email' => filter_input(INPUT_POST, 'email'), + 'description' => filter_input(INPUT_POST, 'description'), + 'contact_person' => filter_input(INPUT_POST, 'contact_person'), + 'phone' => filter_input(INPUT_POST, 'phone'), + 'address' => filter_input(INPUT_POST, 'address'), + 'city' => filter_input(INPUT_POST, 'city'), + 'zip' => filter_input(INPUT_POST, 'zip'), + 'country' => filter_input(INPUT_POST, 'country'), + 'remove_logo' => filter_input(INPUT_POST, 'remove_logo'), + ]; + + // Try to register via H5P core + $core = $plugin->get_h5p_instance('core'); + $result = $core->hubRegisterAccount($formdata, $logo); + + if ($result['success'] === FALSE) { + $core->h5pF->setErrorMessage($result['message']); + http_response_code($result['status_code']); + H5PCore::ajaxError( + $result['message'], $result['error_code'], $result['status_code'] + ); + wp_die(); + } + + $core->h5pF->setInfoMessage($result['message']); + http_response_code(200); + H5PCore::ajaxSuccess($result['message']); + wp_die(); + } +} diff --git a/admin/class-h5p-content-sharing.php b/admin/class-h5p-content-sharing.php new file mode 100644 index 0000000..f4f88ee --- /dev/null +++ b/admin/class-h5p-content-sharing.php @@ -0,0 +1,378 @@ +, Joubel + * @license MIT + * @link http://joubel.com + * @copyright 2022 Joubel + */ + +/** + * H5P Content Sharing class. + * + * @package H5PContentSharing + * @author Oliver Tacke , Joubel + */ +class H5PContentSharing { + + /** + * Display form to register account on the H5P OER Hub. + * + * @since 1.15.5 + * @param int $content_id Content id of content to share. + */ + public static function display_share_content_form($content_id) { + $plugin = H5P_Plugin::get_instance(); + + // Check capability to share content. + if (self::current_user_can_share($content_id) == FALSE) { + H5P_Plugin_Admin::set_error(__('You are not allowed to share this content.', $plugin->get_plugin_slug())); + H5P_Plugin_Admin::print_messages(); + wp_die(); + } + + $plugin = H5P_Plugin::get_instance(); + $core = $plugin->get_h5p_instance('core'); + $content = $plugin->get_content($content_id); + + // Try loading existing info from the HUB + try { + $hubcontent = !empty($content['contentHubId']) ? + $core->hubRetrieveContent($content['contentHubId']) : + NULL; + } + catch (Exception $e) { + H5P_Plugin_Admin::set_error(__(!empty($e->errors) ? + $e->errors : + $e->getMessage(), $plugin->get_plugin_slug())); + H5P_Plugin_Admin::print_messages(); + wp_die(); + } + + // Try to populate with license from content or set defaults + if (empty($content['contentHubId'])) { + $license = isset($content['metadata']['license']) ? + $content['metadata']['license'] : + NULL; + + $licenseVersion = (isset($license) && isset($content['metadata']['licenseVersion'])) ? + $content['metadata']['licenseVersion'] : + NULL; + + $showCopyrightWarning = FALSE; + + // "Undisclosed" and "Copyright" licenses are not allowed on the content hub + if ($license === 'U') { + $license = NULL; + } + + if ($license === 'C') { + $license = NULL; + $showCopyrightWarning = true; + } + + $hubcontent = [ + 'license' => $license, + 'licenseVersion' => $licenseVersion, + 'showCopyrightWarning' => $showCopyrightWarning, + ]; + } + + $language = isset($content['language']) ? + $content['language'] : + $plugin->get_language(); + + /* + * Set settings for H5P Hub sharing client. + * https://github.com/h5p/h5p-hub-sharing-ui + */ + $nonce = wp_create_nonce( 'h5p_content_sharing' ); + $settings = array( + 'h5pContentHubPublish' => array( + 'token' => H5PCore::createToken('content_hub_sharing'), + 'publishURL' => admin_url('admin-ajax.php?action=h5p_hub_sharing&id=' . $content_id . '&_wpnonce=' . $nonce), + 'returnURL' => admin_url('admin.php?page=h5p&task=show&id=' . $content_id), + 'l10n' => $core->getLocalization(), + 'metadata' => json_decode($core->getUpdatedContentHubMetadataCache($language)), + 'title' => $content['title'], + 'contentType' => H5PCore::libraryToString($content['library']), + 'language' => $language, + 'hubContent' => $hubcontent, + 'context' => isset($content['shared']) ? 'edit' : 'publish' + ), + ); + + // Render page + $plugin->print_settings($settings, 'H5POERHubSharing'); + include_once('views/hub-sharing.php'); + H5P_Plugin_Admin::add_style('h5p-css', 'h5p-php-library/styles/h5p.css'); + H5P_Plugin_Admin::add_script('h5p-hub-registration', 'h5p-php-library/js/h5p-hub-sharing.js'); + H5P_Plugin_Admin::add_style('h5p-hub-registration-css', 'h5p-php-library/styles/h5p-hub-sharing.css'); + H5P_Plugin_Admin::add_script('h5p-hub-registration-wp', 'admin/scripts/h5p-hub-sharing.js'); + } + + /** + * Share content on the H5P Hub. + * + * @since 1.15.5 + */ + public static function ajax_hub_sharing() { + $plugin = H5P_Plugin::get_instance(); + + // Load content. + $content = $plugin->get_content(filter_input(INPUT_GET, 'id')); + + // Check capability to share content. + if (self::current_user_can_share($content['id']) == FALSE) { + H5PCore::ajaxError( + __('You are not allowed to share this content.', + $plugin->get_plugin_slug()) + ); + wp_die(); + } + + // Check token + if (!check_ajax_referer( 'h5p_content_sharing', FALSE, FALSE )) { + H5PCore::ajaxError( + __('Invalid security token.', $plugin->get_plugin_slug()) + ); + wp_die(); + } + + // Update Hub status for content before proceeding. + $newstate = self::update_hub_status($content); + $synced = $newstate !== FALSE ? $newstate : intval($content['synced']); + + if ( + isset($content['synced']) && + $content['synced'] === H5PContentHubSyncStatus::WAITING + ) { + H5PCore::ajaxError( + __('Content is being synced.', $plugin->get_plugin_slug()) + ); + wp_die(); + } + + // Add POST fields to redirect to Hub + $data = filter_input_array(INPUT_POST, array( + 'title' => FILTER_UNSAFE_RAW, + 'language' => FILTER_UNSAFE_RAW, + 'level' => FILTER_UNSAFE_RAW, + 'license' => FILTER_UNSAFE_RAW, + 'license_version' => FILTER_UNSAFE_RAW, + 'disciplines' => array( + 'filter' => FILTER_UNSAFE_RAW, + 'flags' => FILTER_REQUIRE_ARRAY, + ), + 'keywords' => array( + 'filter' => FILTER_UNSAFE_RAW, + 'flags' => FILTER_REQUIRE_ARRAY, + ), + 'summary' => FILTER_UNSAFE_RAW, + 'description' => FILTER_UNSAFE_RAW, + 'screenshot_alt_texts' => array( + 'filter' => FILTER_UNSAFE_RAW, + 'flags' => FILTER_REQUIRE_ARRAY, + ), + 'remove_screenshots' => array( + 'filter' => FILTER_UNSAFE_RAW, + 'flags' => FILTER_REQUIRE_ARRAY, + ), + 'remove_icon' => FILTER_UNSAFE_RAW, + 'age' => FILTER_UNSAFE_RAW, + )); + + // Determine export path and file size + $export = $plugin->get_h5p_path() . '/exports/' . ($content['slug'] ? $content['slug'] . '-' : '') . $content['id'] . '.h5p'; + $size = filesize($export); + + // Prepare additional data to POST to Hub + $data['download_url'] = wp_upload_dir()['baseurl'] . '/h5p/exports/' . ($content['slug'] ? $content['slug'] . '-' : '') . $content['id'] . '.h5p'; + $data['size'] = empty($size) ? -1 : $size; + + // Add the icon and any screenshots + $files = array( + 'icon' => !empty($_FILES['icon']) ? $_FILES['icon'] : NULL, + 'screenshots' => !empty($_FILES['screenshots']) ? + $_FILES['screenshots'] : + NULL, + ); + + // Let H5P core share content + $core = $plugin->get_h5p_instance('core'); + try { + $isEdit = !empty($content['contentHubId']); + $updateContent = isset($content['h5p_synced']) && + (int)($content['h5p_synced']) === H5PContentHubSyncStatus::NOT_SYNCED && + $isEdit; + + if ($updateContent) { + // node has been edited since the last time it was published + $data['resync'] = 1; + } + $result = $core->hubPublishContent( + $data, $files, $isEdit ? $content['contentHubId'] : NULL + ); + + $fields = array( + 'shared' => 1, // Content is always shared after sharing or editing + ); + if (!$isEdit) { + $fields['content_hub_id'] = $result->content->id; + // Sync will not happen on 'edit info', only for 'publish' or 'sync'. + $fields['synced'] = H5PContentHubSyncStatus::WAITING; + } + else if ($updateContent) { + $fields['synced'] = H5PContentHubSyncStatus::WAITING; + } + + // Update database fields + $core = $plugin->get_h5p_instance('core'); + $core->h5pF->updateContentFields($content['id'], $fields); + + H5PCore::ajaxSuccess(); + wp_die(); + } + catch (Exception $e) { + H5PCore::ajaxError(!empty($e->errors) ? $e->errors : $e->getMessage()); + wp_die(); + } + } + + /** + * Unshare content. + * + * @since 1.15.5 + * @param int $content_id Content id of content to be unshared. + */ + public static function unshare($content_id) { + $plugin = H5P_Plugin::get_instance(); + + // Check capability to share content. + if (self::current_user_can_share($content_id) == FALSE) { + H5P_Plugin_Admin::set_error(__('You are not allowed to unshare this content.', $plugin->get_plugin_slug())); + H5P_Plugin_Admin::print_messages(); + wp_die(); + } + + $core = $plugin->get_h5p_instance('core'); + $content = $plugin->get_content($content_id); + $success = $core->hubUnpublishContent($content['contentHubId']); + + if ($success) { + $core->h5pF->updateContentFields( + $content['id'], + array('shared' => H5PContentStatus::STATUS_UNPUBLISHED) + ); + } + + H5P_Plugin_Admin::print_messages(); + } + + /** + * Sync content. + * + * @since 1.15.5 + * @param int $content_id Content id of content to be synced. + */ + public static function sync($content_id) { + $plugin = H5P_Plugin::get_instance(); + + // Check capability to share content. + if (self::current_user_can_share($content_id) == FALSE) { + H5P_Plugin_Admin::set_error(__('You are not allowed to sync this content with the Hub.', $plugin->get_plugin_slug())); + H5P_Plugin_Admin::print_messages(); + wp_die(); + } + + $core = $plugin->get_h5p_instance('core'); + $content = $plugin->get_content($content_id); + $exportUrl = wp_upload_dir()['baseurl'] . '/h5p/exports/' . ($content['slug'] ? $content['slug'] . '-' : '') . $content['id'] . '.h5p'; + $hubId = $content['contentHubId']; + $success = $core->hubSyncContent($hubId, $exportUrl); + + if ($success) { + $core->h5pF->updateContentFields( + $content['id'], + array('synced' => H5PContentHubSyncStatus::WAITING) + ); + } + + H5P_Plugin_Admin::print_messages(); + } + + /** + * Update content hub status for shared content. + * + * @since 1.15.5 + * @param array $content Content information. + * @return bool|int False if no change, status id otherwise. + */ + public static function update_hub_status($content = []) { + // Check capability to share content. + if (self::current_user_can_share($content['id']) == FALSE) { + H5P_Plugin_Admin::set_error(__('You are not allowed to share this content.', $plugin->get_plugin_slug())); + H5P_Plugin_Admin::print_messages(); + wp_die(); + } + + $synced = intval($content['synced']); + + // Only check sync status when waiting. + if ( + empty($content['contentHubId']) || + $synced !== H5PContentHubSyncStatus::WAITING + ) { + return FALSE; + } + + $plugin = H5P_Plugin::get_instance(); + $core = $plugin->get_h5p_instance('core'); + + $new_state = $core->getHubContentStatus($content['contentHubId'], $synced); + if ($new_state !== FALSE) { + $core->h5pF->updateContentFields( + $content['id'], + array('synced' => $new_state) + ); + return $new_state; + } + + return FALSE; + } + + /** + * Permission check. Can the current user share the given content. + * + * @since 1.15.5 + * @param int $content_id Content id of content to check capabilities for. + * @return bool True, if current user can share content. + */ + private static function current_user_can_share($content_id) { + if ( + empty(get_option('h5p_hub_is_enabled')) || + empty(get_option('h5p_h5p_site_uuid')) || + empty(get_option('h5p_hub_secret')) + ) { + return FALSE; + } + + // If you can't share content, neither can you share others contents + if (!current_user_can('share_h5p_contents')) { + return FALSE; + } + if (current_user_can('share_others_h5p_contents')) { + return TRUE; + } + + $plugin = H5P_Plugin::get_instance(); + $content = $plugin->get_content($content_id); + $author_id = (int)(is_array($content) ? + $content['user_id'] : + $content->user_id); + + return get_current_user_id() === $author_id; + } +} diff --git a/admin/class-h5p-plugin-admin.php b/admin/class-h5p-plugin-admin.php index c96e6ac..976219b 100644 --- a/admin/class-h5p-plugin-admin.php +++ b/admin/class-h5p-plugin-admin.php @@ -67,6 +67,8 @@ private function __construct() { $this->content = new H5PContentAdmin($this->plugin_slug); $this->library = new H5PLibraryAdmin($this->plugin_slug); $this->privacy = new H5PPrivacyPolicy($this->plugin_slug); + $this->content_hub_registration = + new H5PContentHubRegistration($this->plugin_slug); // Initialize admin area. $this->add_privacy_features(); @@ -123,6 +125,12 @@ private function __construct() { // AJAX for restricting library access add_action('wp_ajax_h5p_restrict_library', array($this->library, 'ajax_restrict_access')); + // OER Hub + add_action('wp_ajax_h5p_register_account', array($this->content_hub_registration, 'ajax_register_account')); + add_action('wp_ajax_h5p_content-hub-metadata-cache', array($this->content, 'ajax_h5p_content_hub_metadata_cache')); + add_action('wp_ajax_h5p_get-content', array($this->content, 'ajax_h5p_get_content')); + add_action('wp_ajax_h5p_hub_sharing', array($this, 'handle_ajax_hub_sharing')); + // Display admin notices add_action('admin_notices', array($this, 'admin_notices')); @@ -134,6 +142,15 @@ private function __construct() { add_action('deleted_user', array($this, 'deleted_user')); } + /** + * Handle ajax hub sharing. + * + * @since 1.15.5 + */ + public function handle_ajax_hub_sharing() { + H5PContentSharing::ajax_hub_sharing(); + } + /** * Display a form for adding and editing h5p content. * @@ -432,112 +449,130 @@ public function add_plugin_admin_menu() { * @since 1.0.0 */ public function display_settings_page() { - $save = filter_input(INPUT_POST, 'save_these_settings'); - if ($save !== NULL) { - // Get input and store settings - check_admin_referer('h5p_settings', 'save_these_settings'); // Verify form - - // Action bar - $frame = filter_input(INPUT_POST, 'frame', FILTER_VALIDATE_BOOLEAN); - update_option('h5p_frame', $frame); - - $download = filter_input(INPUT_POST, 'download', FILTER_VALIDATE_INT); - update_option('h5p_export', $download); - - $embed = filter_input(INPUT_POST, 'embed', FILTER_VALIDATE_INT); - update_option('h5p_embed', $embed); - - $copyright = filter_input(INPUT_POST, 'copyright', FILTER_VALIDATE_BOOLEAN); - update_option('h5p_copyright', $copyright); - - $about = filter_input(INPUT_POST, 'about', FILTER_VALIDATE_BOOLEAN); - update_option('h5p_icon', $about); - - $track_user = filter_input(INPUT_POST, 'track_user', FILTER_VALIDATE_BOOLEAN); - update_option('h5p_track_user', $track_user); - - $save_content_state = filter_input(INPUT_POST, 'save_content_state', FILTER_VALIDATE_BOOLEAN); - update_option('h5p_save_content_state', $save_content_state); - - $save_content_frequency = filter_input(INPUT_POST, 'save_content_frequency', FILTER_VALIDATE_INT); - update_option('h5p_save_content_frequency', $save_content_frequency); - - $show_toggle_view_others_h5p_contents = filter_input(INPUT_POST, 'show_toggle_view_others_h5p_contents', FILTER_VALIDATE_INT); - update_option('h5p_show_toggle_view_others_h5p_contents', $show_toggle_view_others_h5p_contents); + switch (filter_input(INPUT_GET, 'task')) { + case NULL: { + $save = filter_input(INPUT_POST, 'save_these_settings'); + if ($save !== NULL) { + // Get input and store settings + check_admin_referer('h5p_settings', 'save_these_settings'); // Verify form + + // Action bar + $frame = filter_input(INPUT_POST, 'frame', FILTER_VALIDATE_BOOLEAN); + update_option('h5p_frame', $frame); + + $download = filter_input(INPUT_POST, 'download', FILTER_VALIDATE_INT); + update_option('h5p_export', $download); + + $embed = filter_input(INPUT_POST, 'embed', FILTER_VALIDATE_INT); + update_option('h5p_embed', $embed); + + $copyright = filter_input(INPUT_POST, 'copyright', FILTER_VALIDATE_BOOLEAN); + update_option('h5p_copyright', $copyright); + + $about = filter_input(INPUT_POST, 'about', FILTER_VALIDATE_BOOLEAN); + update_option('h5p_icon', $about); + + $track_user = filter_input(INPUT_POST, 'track_user', FILTER_VALIDATE_BOOLEAN); + update_option('h5p_track_user', $track_user); + + $save_content_state = filter_input(INPUT_POST, 'save_content_state', FILTER_VALIDATE_BOOLEAN); + update_option('h5p_save_content_state', $save_content_state); + + $save_content_frequency = filter_input(INPUT_POST, 'save_content_frequency', FILTER_VALIDATE_INT); + update_option('h5p_save_content_frequency', $save_content_frequency); + + $show_toggle_view_others_h5p_contents = filter_input(INPUT_POST, 'show_toggle_view_others_h5p_contents', FILTER_VALIDATE_INT); + update_option('h5p_show_toggle_view_others_h5p_contents', $show_toggle_view_others_h5p_contents); + + $insert_method = filter_input(INPUT_POST, 'insert_method', FILTER_SANITIZE_SPECIAL_CHARS); + update_option('h5p_insert_method', $insert_method); + + $enable_lrs_content_types = filter_input(INPUT_POST, 'enable_lrs_content_types', FILTER_VALIDATE_BOOLEAN); + update_option('h5p_enable_lrs_content_types', $enable_lrs_content_types); + + // TODO: Make it possible to change site key + // $site_key = filter_input(INPUT_POST, 'site_key', FILTER_SANITIZE_SPECIAL_CHARS); + // if (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $site_key)) { + // // This appears to be a valid UUID, lets use it! + // update_option('h5p_site_key', $site_key); + // } + // else { + // // Invalid key, use the old one + // $site_key = get_option('h5p_site_key', get_option('h5p_h5p_site_uuid', FALSE)); + // } + + $enable_hub = filter_input(INPUT_POST, 'enable_hub', FILTER_VALIDATE_BOOLEAN); + $is_hub_enabled = get_option('h5p_hub_is_enabled', TRUE) ? TRUE : NULL; + if ($enable_hub !== $is_hub_enabled) { + // Changed, update core + $plugin = H5P_Plugin::get_instance(); + $core = $plugin->get_h5p_instance('core'); + $core->fetchLibrariesMetadata($enable_hub === NULL); + } + update_option('h5p_hub_is_enabled', $enable_hub); - $insert_method = filter_input(INPUT_POST, 'insert_method', FILTER_SANITIZE_SPECIAL_CHARS); - update_option('h5p_insert_method', $insert_method); + $send_usage_statistics = filter_input(INPUT_POST, 'send_usage_statistics', FILTER_VALIDATE_BOOLEAN); + update_option('h5p_send_usage_statistics', $send_usage_statistics); + } + else { + $frame = get_option('h5p_frame', TRUE); + $download = get_option('h5p_export', TRUE); + $embed = get_option('h5p_embed', TRUE); + $copyright = get_option('h5p_copyright', TRUE); + $about = get_option('h5p_icon', TRUE); + $track_user = get_option('h5p_track_user', TRUE); + $save_content_state = get_option('h5p_save_content_state', FALSE); + $save_content_frequency = get_option('h5p_save_content_frequency', 30); + $show_toggle_view_others_h5p_contents = get_option('h5p_show_toggle_view_others_h5p_contents', 0); + $insert_method = get_option('h5p_insert_method', 'id'); + $enable_lrs_content_types = get_option('h5p_enable_lrs_content_types', FALSE); + $enable_hub = get_option('h5p_hub_is_enabled', TRUE); + // $site_key = get_option('h5p_site_key', get_option('h5p_h5p_site_uuid', FALSE)); + $send_usage_statistics = get_option('h5p_send_usage_statistics', TRUE); + } - $enable_lrs_content_types = filter_input(INPUT_POST, 'enable_lrs_content_types', FILTER_VALIDATE_BOOLEAN); - update_option('h5p_enable_lrs_content_types', $enable_lrs_content_types); + $plugin = H5P_Plugin::get_instance(); + $core = $plugin->get_h5p_instance('core'); - // TODO: Make it possible to change site key -// $site_key = filter_input(INPUT_POST, 'site_key', FILTER_SANITIZE_SPECIAL_CHARS); -// if (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $site_key)) { -// // This appears to be a valid UUID, lets use it! -// update_option('h5p_site_key', $site_key); -// } -// else { -// // Invalid key, use the old one -// $site_key = get_option('h5p_site_key', get_option('h5p_h5p_site_uuid', FALSE)); -// } + try { + $accountInfo = $core->hubAccountInfo(); + } + catch (Exception $e) { + // Not showing account form before secret has been fixed + } - $enable_hub = filter_input(INPUT_POST, 'enable_hub', FILTER_VALIDATE_BOOLEAN); - $is_hub_enabled = get_option('h5p_hub_is_enabled', TRUE) ? TRUE : NULL; - if ($enable_hub !== $is_hub_enabled) { - // Changed, update core - $plugin = H5P_Plugin::get_instance(); - $core = $plugin->get_h5p_instance('core'); - $core->fetchLibrariesMetadata($enable_hub === NULL); + // Attach disable hub configuration + + // Get error messages + $errors = $core->checkSetupErrorMessage()->errors; + $disableHubData = array( + 'errors' => $errors, + 'header' => $core->h5pF->t('Confirmation action'), + 'confirmationDialogMsg' => $core->h5pF->t('Do you still want to enable the hub ?'), + 'cancelLabel' => $core->h5pF->t('Cancel'), + 'confirmLabel' => $core->h5pF->t('Confirm') + ); + $plugin->print_settings($disableHubData, 'H5PDisableHubData'); + + include_once('views/settings.php'); + H5P_Plugin_Admin::add_script('h5p-jquery', 'h5p-php-library/js/jquery.js'); + H5P_Plugin_Admin::add_script('h5p-event-dispatcher', 'h5p-php-library/js/h5p-event-dispatcher.js'); + H5P_Plugin_Admin::add_script('h5p-confirmation-dialog', 'h5p-php-library/js/h5p-confirmation-dialog.js'); + H5P_Plugin_Admin::add_script('h5p-disable-hub', 'h5p-php-library/js/settings/h5p-disable-hub.js'); + H5P_Plugin_Admin::add_script('h5p-display-options', 'h5p-php-library/js/h5p-display-options.js'); + H5P_Plugin_Admin::add_style('h5p-confirmation-dialog-css', 'h5p-php-library/styles/h5p-confirmation-dialog.css'); + H5P_Plugin_Admin::add_style('h5p-css', 'h5p-php-library/styles/h5p.css'); + H5P_Plugin_Admin::add_style('h5p-core-button-css', 'h5p-php-library/styles/h5p-core-button.css'); + + new H5P_Event('settings'); + return; } - update_option('h5p_hub_is_enabled', $enable_hub); - $send_usage_statistics = filter_input(INPUT_POST, 'send_usage_statistics', FILTER_VALIDATE_BOOLEAN); - update_option('h5p_send_usage_statistics', $send_usage_statistics); - } - else { - $frame = get_option('h5p_frame', TRUE); - $download = get_option('h5p_export', TRUE); - $embed = get_option('h5p_embed', TRUE); - $copyright = get_option('h5p_copyright', TRUE); - $about = get_option('h5p_icon', TRUE); - $track_user = get_option('h5p_track_user', TRUE); - $save_content_state = get_option('h5p_save_content_state', FALSE); - $save_content_frequency = get_option('h5p_save_content_frequency', 30); - $show_toggle_view_others_h5p_contents = get_option('h5p_show_toggle_view_others_h5p_contents', 0); - $insert_method = get_option('h5p_insert_method', 'id'); - $enable_lrs_content_types = get_option('h5p_enable_lrs_content_types', FALSE); - $enable_hub = get_option('h5p_hub_is_enabled', TRUE); -// $site_key = get_option('h5p_site_key', get_option('h5p_h5p_site_uuid', FALSE)); - $send_usage_statistics = get_option('h5p_send_usage_statistics', TRUE); + case 'register': { + H5PContentHubRegistration::display_register_accout_form(); + return; + } } - - // Attach disable hub configuration - $plugin = H5P_Plugin::get_instance(); - $core = $plugin->get_h5p_instance('core'); - - // Get error messages - $errors = $core->checkSetupErrorMessage()->errors; - $disableHubData = array( - 'errors' => $errors, - 'header' => $core->h5pF->t('Confirmation action'), - 'confirmationDialogMsg' => $core->h5pF->t('Do you still want to enable the hub ?'), - 'cancelLabel' => $core->h5pF->t('Cancel'), - 'confirmLabel' => $core->h5pF->t('Confirm') - ); - $plugin->print_settings($disableHubData, 'H5PDisableHubData'); - - include_once('views/settings.php'); - H5P_Plugin_Admin::add_script('h5p-jquery', 'h5p-php-library/js/jquery.js'); - H5P_Plugin_Admin::add_script('h5p-event-dispatcher', 'h5p-php-library/js/h5p-event-dispatcher.js'); - H5P_Plugin_Admin::add_script('h5p-confirmation-dialog', 'h5p-php-library/js/h5p-confirmation-dialog.js'); - H5P_Plugin_Admin::add_script('h5p-disable-hub', 'h5p-php-library/js/settings/h5p-disable-hub.js'); - H5P_Plugin_Admin::add_script('h5p-display-options', 'h5p-php-library/js/h5p-display-options.js'); - H5P_Plugin_Admin::add_style('h5p-confirmation-dialog-css', 'h5p-php-library/styles/h5p-confirmation-dialog.css'); - H5P_Plugin_Admin::add_style('h5p-css', 'h5p-php-library/styles/h5p.css'); - H5P_Plugin_Admin::add_style('h5p-core-button-css', 'h5p-php-library/styles/h5p-core-button.css'); - - new H5P_Event('settings'); } /** diff --git a/admin/scripts/h5p-editor.js b/admin/scripts/h5p-editor.js index f956691..8e2b2a6 100644 --- a/admin/scripts/h5p-editor.js +++ b/admin/scripts/h5p-editor.js @@ -17,6 +17,12 @@ var ns = H5PEditor; // Required styles and scripts for the editor H5PEditor.assets = H5PIntegration.editor.assets; + // Content Hub + H5PEditor.enableContentHub = H5PIntegration.editor.enableContentHub; + H5PIntegration.Hub = { + contentSearchUrl: H5PIntegration.editor.hub.contentSearchUrl + }; + // Required for assets H5PEditor.baseUrl = ''; diff --git a/admin/scripts/h5p-hub-registration.js b/admin/scripts/h5p-hub-registration.js new file mode 100644 index 0000000..c974abd --- /dev/null +++ b/admin/scripts/h5p-hub-registration.js @@ -0,0 +1,24 @@ +(function () { + var wasInitialized = false; + + function initialize() { + if (wasInitialized) { + return; + } + + wasInitialized = true; + + var data = H5POERHubRegistration.H5PContentHubRegistration; + data.container = document.getElementById('h5p-hub-registration'); + H5PHub.createRegistrationUI(data); + } + + if (document.readyState !== 'loading') { + initialize(); + } + else { + document.addEventListener('readystatechange', function () { + initialize(); + }); + } +})() diff --git a/admin/scripts/h5p-hub-sharing.js b/admin/scripts/h5p-hub-sharing.js new file mode 100644 index 0000000..92d1445 --- /dev/null +++ b/admin/scripts/h5p-hub-sharing.js @@ -0,0 +1,26 @@ +(function () { + var wasInitialized = false; + + function initialize() { + if (wasInitialized) { + return; + } + + wasInitialized = true; + + var publish = document.getElementById('h5p-hub-sharing'); + if (!publish.classList.contains('processed')) { + publish.classList.add('processed'); + H5PHub.createSharingUI(publish, H5POERHubSharing.h5pContentHubPublish); + } + } + + if (document.readyState !== 'loading') { + initialize(); + } + else { + document.addEventListener('readystatechange', function () { + initialize(); + }); + } +})() diff --git a/admin/styles/admin.css b/admin/styles/admin.css index bf9331d..0499e5e 100644 --- a/admin/styles/admin.css +++ b/admin/styles/admin.css @@ -235,7 +235,54 @@ .content-upgrade-log { color: red; } +#h5p-hub-registration .h5p-hub-registration-wrapper { + background-color: #fff; +} +.h5p-settings-container .h5p-settings-hub-registration-wrapper { + border-bottom: 1px solid #e2e5ee; + display: flex; + flex-direction: row; + width: 100%; +} +.h5p-settings-container .h5p-settings-hub-registration-logo-wrapper { + margin-right: 2rem; + min-width: 6rem; + width: 30%; + max-width: 10rem; +} +.h5p-settings-container .h5p-settings-hub-registration-logo-wrapper > img { + padding: .5rem 0 0 0; + width: 100%; +} +.h5p-settings-container .h5p-settings-hub-registration-data-wrapper { + display: flex; + flex-direction: column; + width: 100%; +} +.h5p-settings-container .h5p-settings-hub-registration-data-wrapper tbody > tr:nth-child(1) > th, +.h5p-settings-container .h5p-settings-hub-registration-data-wrapper tbody > tr:nth-child(1) > td { + border-top: 0; +} +.h5p-settings-container .h5p-settings-hub-registration-data-wrapper th { + border-top: 1px solid #e2e5ee; + color: #3c4859; + cursor: default; + font-weight: bold; + padding: .5rem 0; + min-width: 10rem; + width: 15rem; +} +.h5p-settings-container .h5p-settings-hub-registration-data-wrapper td { + border-top: 1px solid #e2e5ee; + padding: .5rem 0; +} +.h5p-settings-container .h5p-settings-hub-registration-change-data { +} + +#h5p-hub-publish-modal-wrapper { + background-color: #fff; +} @media screen and (max-width: 782px) { #wpbody-content .wrap { overflow-x: auto; diff --git a/admin/views/hub-registration.php b/admin/views/hub-registration.php new file mode 100644 index 0000000..2eae729 --- /dev/null +++ b/admin/views/hub-registration.php @@ -0,0 +1,12 @@ +, Joubel + * @license MIT + * @link http://joubel.com + * @copyright 2022 Joubel + */ +?> +
diff --git a/admin/views/hub-sharing.php b/admin/views/hub-sharing.php new file mode 100644 index 0000000..bb4a472 --- /dev/null +++ b/admin/views/hub-sharing.php @@ -0,0 +1,12 @@ +, Joubel + * @license MIT + * @link http://joubel.com + * @copyright 2022 Joubel + */ +?> +
diff --git a/admin/views/settings.php b/admin/views/settings.php index 817ca58..03c95ac 100644 --- a/admin/views/settings.php +++ b/admin/views/settings.php @@ -8,6 +8,16 @@ * @link http://joubel.com * @copyright 2014 Joubel */ + +/** + * Check plain text. + * + * @param string $text Text. + * @return string Plain text. + */ +function check_plain($text) { + return htmlspecialchars((string) $text, ENT_QUOTES, 'UTF-8'); +} ?>
@@ -207,10 +217,102 @@ class="h5p-settings-disable-hub-checkbox" plugin_slug); ?>

- data is collected on h5p.org.", $this->plugin_slug), array('a' => array('href' => array(), 'target' => array()))), 'https://h5p.org/tracking-the-usage-of-h5p'); ?> + data is collected on h5p.org.", $this->plugin_slug),array('a' => array('href' => array(), 'target' => array()))),'https://h5p.org/tracking-the-usage-of-h5p'); ?>

+ + + plugin_slug); ?> + + + +

+ plugin_slug)); ?> +

+ + +

+ Register an account on the H5P Hub", $this->plugin_slug), array('a' => array('href' => array()))), admin_url('options-general.php?page=h5p_settings&task=register&_wpnonce=' . wp_create_nonce( 'h5p_content_hub_registration_form' ))); ?> +

+ +
+ logo)): ?> +
+ +
+ + + + + name)): ?> + + + + + + + contactPerson)): ?> + + + + + + + email)): ?> + + + + + + + address)): ?> + + + + + + + zip)): ?> + + + + + + + city)): ?> + + + + + + + country)): ?> + + + + + + + phone)): ?> + + + + + + + +
plugin_slug)) ?>name))?>
plugin_slug)) ?>contactPerson))?>
plugin_slug)) ?>email))?>
plugin_slug)) ?>address))?>
plugin_slug)) ?>zip))?>
plugin_slug)) ?>city))?>
plugin_slug)) ?>country))?>
plugin_slug)) ?>phone))?>
+
+

+ Change account settings", $this->plugin_slug), array('a' => array('href' => array()))), admin_url('options-general.php?page=h5p_settings&task=register&_wpnonce=' . wp_create_nonce( 'h5p_content_hub_registration_form' ))); ?> +

+ + + + + + diff --git a/admin/views/show-content.php b/admin/views/show-content.php index 594deb2..c5ae825 100644 --- a/admin/views/show-content.php +++ b/admin/views/show-content.php @@ -19,6 +19,15 @@ current_user_can_edit($this->content)): ?> plugin_slug); ?> + current_user_can_share($this->content)): ?> + is_content_shared()): ?> + plugin_slug); ?> + plugin_slug); ?> + plugin_slug); ?> + + plugin_slug); ?> + +
diff --git a/autoloader.php b/autoloader.php index cd36648..615032a 100644 --- a/autoloader.php +++ b/autoloader.php @@ -41,7 +41,9 @@ function h5p_autoloader($class) { 'H5PLibraryAdmin' => 'admin/class-h5p-library-admin.php', 'H5PEditorWordPressStorage' => 'admin/class-h5p-editor-wordpress-storage.php', 'H5PEditorWordPressAjax' => 'admin/class-h5p-editor-wordpress-ajax.php', - 'H5PPrivacyPolicy' => 'admin/class-h5p-privacy-policy.php' + 'H5PPrivacyPolicy' => 'admin/class-h5p-privacy-policy.php', + 'H5PContentHubRegistration' => 'admin/class-h5p-content-hub-registration.php', + 'H5PContentSharing' => 'admin/class-h5p-content-sharing.php' ); } diff --git a/public/class-h5p-plugin.php b/public/class-h5p-plugin.php index f8cbc15..ce9b931 100644 --- a/public/class-h5p-plugin.php +++ b/public/class-h5p-plugin.php @@ -24,7 +24,7 @@ class H5P_Plugin { * @since 1.0.0 * @var string */ - const VERSION = '1.16.0'; + const VERSION = '1.16.1'; /** * The Unique identifier for this plugin. @@ -218,6 +218,9 @@ public static function update_database() { changes LONGTEXT NULL, default_language VARCHAR(32) NULL, a11y_title VARCHAR(255) NULL, + shared TINYINT UNSIGNED NOT NULL DEFAULT 0, + synced TINYINT UNSIGNED NULL, + content_hub_id BIGINT UNSIGNED NULL, PRIMARY KEY (id) ) {$charset};"); @@ -380,6 +383,12 @@ public static function update_database() { KEY created_at (created_at) ) {$charset};"); + dbDelta("CREATE TABLE {$wpdb->prefix}h5p_content_hub_metadata_cache ( + language VARCHAR(31) UNIQUE NOT NULL, + json LONGTEXT NULL, + last_checked INT UNSIGNED NULL + ) {$charset};"); + // Add default setting options add_option('h5p_frame', TRUE); add_option('h5p_export', TRUE); @@ -466,6 +475,8 @@ public static function check_for_updates() { $pre_1113 = ($v->major < 1 || ($v->major === 1 && $v->minor < 11) || ($v->major === 1 && $v->minor === 11 && $v->patch < 3)); // < 1.11.3 $pre_1150 = ($v->major < 1 || ($v->major === 1 && $v->minor < 15)); // < 1.15.0 + $pre_1155 = ($v->major < 1 || ($v->major === 1 && $v->minor < 15) || + ($v->major === 1 && $v->minor === 15 && $v->patch < 5)); // < 1.15.5 // Run version specific updates if ($pre_120) { @@ -482,6 +493,10 @@ public static function check_for_updates() { // Does only add new permissions self::upgrade_1150(); } + if ($pre_1155) { + // Does only add new permissions + self::upgrade_1155(); + } } if ($pre_180) { @@ -609,6 +624,27 @@ public static function upgrade_1150() { } } + /** + * Add new permissions for content hub registration and content sharing. + * + * @since 1.15.5 + * @global \WP_Roles $wp_roles + */ + public static function upgrade_1155() { + global $wp_roles; + if (!isset($wp_roles)) { + $wp_roles = new WP_Roles(); + } + + $all_roles = $wp_roles->roles; + foreach ($all_roles as $role_name => $role_info) { + $role = get_role($role_name); + self::map_capability($role, $role_info, 'manage_options', 'manage_h5p_content_hub_registration'); + self::map_capability($role, $role_info, 'edit_others_h5p_contents', 'share_others_h5p_contents'); + self::map_capability($role, $role_info, 'edit_h5p_contents', 'share_h5p_contents'); + } + } + /** * Remove duplicate keys that might have been created by a bug in dbDelta. * @@ -658,9 +694,12 @@ public static function assign_capabilities() { self::map_capability($role, $role_info, 'install_plugins', 'disable_h5p_security'); } self::map_capability($role, $role_info, 'manage_options', 'manage_h5p_libraries'); + self::map_capability($role, $role_info, 'manage_options', 'manage_h5p_content_hub_registration'); self::map_capability($role, $role_info, 'edit_others_pages', 'install_recommended_h5p_libraries'); self::map_capability($role, $role_info, 'edit_others_pages', 'edit_others_h5p_contents'); + self::map_capability($role, $role_info, 'edit_others_pages', 'share_others_h5p_contents'); self::map_capability($role, $role_info, 'edit_posts', 'edit_h5p_contents'); + self::map_capability($role, $role_info, 'edit_posts', 'share_h5p_contents'); self::map_capability($role, $role_info, 'read', 'view_others_h5p_contents'); self::map_capability($role, $role_info, 'read', 'view_h5p_contents'); self::map_capability($role, $role_info, 'read', 'view_h5p_results'); @@ -1607,6 +1646,7 @@ public static function uninstall() { $wpdb->query("DROP TABLE {$wpdb->prefix}h5p_counters"); $wpdb->query("DROP TABLE {$wpdb->prefix}h5p_events"); $wpdb->query("DROP TABLE {$wpdb->prefix}h5p_tmpfiles"); + $wpdb->query("DROP TABLE {$wpdb->prefix}h5p_content_hub_metadata_cache"); // Remove settings delete_option('h5p_version'); diff --git a/public/class-h5p-wordpress.php b/public/class-h5p-wordpress.php index 58b8b14..510608b 100644 --- a/public/class-h5p-wordpress.php +++ b/public/class-h5p-wordpress.php @@ -605,26 +605,30 @@ public function loadLibrary($name, $majorVersion, $minorVersion) { ARRAY_A ); - $dependencies = $wpdb->get_results($wpdb->prepare( - "SELECT hl.name as machineName, hl.major_version as majorVersion, hl.minor_version as minorVersion, hll.dependency_type as dependencyType - FROM {$wpdb->prefix}h5p_libraries_libraries hll - JOIN {$wpdb->prefix}h5p_libraries hl ON hll.required_library_id = hl.id - WHERE hll.library_id = %d", - $library['libraryId']) - ); - foreach ($dependencies as $dependency) { - $library[$dependency->dependencyType . 'Dependencies'][] = array( - 'machineName' => $dependency->machineName, - 'majorVersion' => $dependency->majorVersion, - 'minorVersion' => $dependency->minorVersion, - ); - } - if ($this->isInDevMode()) { - $semantics = $this->getSemanticsFromFile($library['machineName'], $library['majorVersion'], $library['minorVersion']); - if ($semantics) { - $library['semantics'] = $semantics; + // TODO: Fix independent of OER-Hub + if (!empty($library)) { + $dependencies = $wpdb->get_results($wpdb->prepare( + "SELECT hl.name as machineName, hl.major_version as majorVersion, hl.minor_version as minorVersion, hll.dependency_type as dependencyType + FROM {$wpdb->prefix}h5p_libraries_libraries hll + JOIN {$wpdb->prefix}h5p_libraries hl ON hll.required_library_id = hl.id + WHERE hll.library_id = %d", + $library['libraryId']) + ); + foreach ($dependencies as $dependency) { + $library[$dependency->dependencyType . 'Dependencies'][] = array( + 'machineName' => $dependency->machineName, + 'majorVersion' => $dependency->majorVersion, + 'minorVersion' => $dependency->minorVersion, + ); + } + if ($this->isInDevMode()) { + $semantics = $this->getSemanticsFromFile($library['machineName'], $library['majorVersion'], $library['minorVersion']); + if ($semantics) { + $library['semantics'] = $semantics; + } } } + return $library; } @@ -712,6 +716,9 @@ public function loadContent($id) { , hc.changes AS changes , hc.default_language AS defaultLanguage , hc.a11y_title AS a11yTitle + , hc.shared AS shared + , hc.synced AS synced + , hc.content_hub_id AS contentHubId FROM {$wpdb->prefix}h5p_contents hc JOIN {$wpdb->prefix}h5p_libraries hl ON hl.id = hc.library_id WHERE hc.id = %d", @@ -935,35 +942,132 @@ public function getPlatformInfo() { } /** - * Implements fetchExternalData + * Function to multipart encode a uploaded file. + * + * @param string $name The form data name. + * @param string $filepath Path to file location (tmp_name). + * @param string $filename Original file name (as posted). + * @param string $mimetype Original mime type (as posted). + * + * @return string A string with the mimetype and the file. */ - public function fetchExternalData($url, $data = NULL, $blocking = TRUE, $stream = NULL, $fullData = FALSE, $headers = array(), $files = array(), $method = 'POST') { + function h5p_multipart_enc_file($name, $filepath, $filename, $mimetype = 'application/octet-stream') { + if (substr($filepath, 0, 1) === '@') { + $filepath = substr($filepath, 1); + } + $data = "Content-Disposition: form-data; name=\"$name\"; filename=\"$filename\"\r\n"; // "file" key. + $data .= "Content-Transfer-Encoding: binary\r\n"; + $data .= "Content-Type: $mimetype\r\n\r\n"; + + $data .= file_get_contents($filepath) . "\r\n"; + return $data; + } + + /** + * Function to encode text data. + * + * @param string $name The name of the field. + * @param string $value The value of the field. + * @return string A string encoded text data. + */ + function h5p_multipart_enc_text($name, $value){ + return "Content-Disposition: form-data; name=\"$name\"\r\n\r\n$value\r\n"; // TODO: Should we be using rawurlencode ? + } + + /** + * Implements fetchExternalData. + * + * @param string $url Where you want to get or send data. + * @param array $data Data to post to the URL. + * @param bool $blocking Set to 'FALSE' to instantly time out (fire and forget). + * @param string $stream Path to where the file should be saved. + * @param bool $fullData Return additional response data such as headers and potentially other data + * @param array $headers Headers to send + * + * @return string|array The content (response body), or an array with data. NULL if something went wrong + */ + public function fetchExternalData($url, $data = NULL, $blocking = TRUE, $stream = NULL, $fullData = FALSE, $headers = [], $files = [], $method = 'POST') { @set_time_limit(0); $options = array( 'timeout' => !empty($blocking) ? 30 : 0.01, 'stream' => !empty($stream), - 'filename' => !empty($stream) ? $stream : FALSE + 'filename' => !empty($stream) ? $stream : FALSE, + 'headers' => $headers ); - if ($data !== NULL) { - // Post - $options['body'] = $data; - $response = wp_remote_post($url, $options); - } - else { - // Get + if (!empty($files)) { + // TODO: Is this required on WordPress? Better option? + + // We have to use multipart form-data encoding with boundary since the + // old drupal http client does not natively support posting files + $boundary = md5(uniqid('', TRUE)); + $options['method'] = $method; + $encoded_data = ''; + foreach ($data as $key => $value) { + if (empty($value)) { + continue; + } - if (empty($options['filename'])) { - // Support redirects - $response = wp_remote_get($url); + if (is_array($value)) { + foreach ($value as $val) { + $encoded_data .= "--" . $boundary . "\r\n"; + $encoded_data .= $this->h5p_multipart_enc_text($key . '[]', $val); + } + } + else { + $encoded_data .= "--" . $boundary . "\r\n"; + $encoded_data .= $this->h5p_multipart_enc_text($key, $value); + } } - else { - // Use safe when downloading files - $response = wp_safe_remote_get($url, $options); + + // TODO: Should we check $_FILES[]['size'] (+ combiend size) before trying to post something we know is too large? + foreach ($files as $name => $file) { + if ($file === NULL) { + continue; + } + elseif (is_array($file['name'])) { + // Array of files uploaded (multiple) + for ($i = 0; $i < count($file['name']); $i++) { + $encoded_data .= "--" . $boundary . "\r\n"; + $encoded_data .= $this->h5p_multipart_enc_file($name . '[]', $file['tmp_name'][$i], $file['name'][$i], $file['type'][$i]); + } + } + else { + // Single file + $encoded_data .= "--" . $boundary . "\r\n"; + $encoded_data .= $this->h5p_multipart_enc_file($name, $file['tmp_name'], $file['name'], $file['type']); + } } + + $encoded_data .= "--" . $boundary . "--"; + $options['body'] = $encoded_data; + $options['headers'] = array_merge(array( + 'Content-Type' => 'multipart/form-data; boundary=' . $boundary + ), $options['headers']); + } + elseif (!empty($data)) { + $options['method'] = $method; + $options['headers'] = array_merge(array( + 'Content-Type' => 'application/x-www-form-urlencoded' + ), $options['headers']); + $options['body'] = $data; } - if (is_wp_error($response)) { + if (!empty($options['filename']) && empty($data)) { + $response = wp_safe_remote_get($url, $options); + } + else { + $response = wp_remote_request($url, $options); + } + + if ($fullData) { + return [ + 'status' => intval($response['response']['code']), + 'data' => isset($response['body']) ? $response['body'] : NULL, + 'headers' => isset($response['headers']) ? $response['headers'] : NULL + ]; + } + else if (is_wp_error($response)) { $this->setErrorMessage($response->get_error_message(), 'failed-fetching-external-data'); return FALSE; } @@ -1304,9 +1408,133 @@ public function libraryHasUpgrade($library) { )) !== NULL; } - // Content hub not implemented in Wordpress, ignore abstract functions - public function replaceContentHubMetadataCache($metadata, $lang) { return []; } - public function getContentHubMetadataCache($lang = 'en') { die('Called'); return json_encode([]); } - public function getContentHubMetadataChecked($lang = 'en') {return []; } - public function setContentHubMetadataChecked($time, $lang = 'en') { return []; } + /** + * Implements replaceContentHubMetadataCache + * + * @param JsonSerializable $metadata Metadata as received from content hub + * @param string $lang Language in ISO 639-1 + * + * @return mixed + */ + public function replaceContentHubMetadataCache($metadata, $lang = 'en') { + global $wpdb; + + $count = $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(*) + FROM {$wpdb->prefix}h5p_content_hub_metadata_cache + WHERE language = '%s' + LIMIT 1", + $lang + )); + + if ($count === '0') { + $result = $wpdb->insert( + "{$wpdb->prefix}h5p_content_hub_metadata_cache", + array( + 'language' => $lang, + 'json' => $metadata + ), + array('%s', '%s') + ); + } + else { + $result = $wpdb->update( + "{$wpdb->prefix}h5p_content_hub_metadata_cache", + array('json' => $metadata), + array('language' => $lang), + array('%s'), + array('%s') + ); + } + } + + /** + * Implements libraryHasUpgrade + * Get content hub metadata cache as json from db + * + * @param string $lang + * + * @return JsonSerializable|NULL + */ + public function getContentHubMetadataCache($lang = 'en') { + global $wpdb; + + return $wpdb->get_var($wpdb->prepare( + "SELECT json + FROM {$wpdb->prefix}h5p_content_hub_metadata_cache + WHERE language = '%s' + LIMIT 1", + $lang + )); + } + + /** + * Get when content hub metadata cache was last checked + * + * @param string $lang ISO 639-1 lang code + * + * @return string|NULL Returns time in RFC 7231 format + */ + public function getContentHubMetadataChecked($lang = 'en') { + global $wpdb; + + $results = $wpdb->get_var($wpdb->prepare( + "SELECT last_checked + FROM {$wpdb->prefix}h5p_content_hub_metadata_cache + WHERE language = '%s' + LIMIT 1", + $lang + )); + + if (!empty($results)) { + $time = new DateTime(); + $time->setTimestamp($results); + $results = $time->format("D, d M Y H:i:s \G\M\T"); + } + + return $results; + } + + /** + * Set when content hub metadata cache was last checked. + * + * @param int|NULL $time UNIX timestamp for when last check + * @param string $lang ISO 639-1 lang code + * + * @return bool + */ + public function setContentHubMetadataChecked($time, $lang = 'en') { + global $wpdb; + + $result = $wpdb->update( + "{$wpdb->prefix}h5p_content_hub_metadata_cache", + array('last_checked' => $time), + array('language' => $lang), + array('%d'), + array('%s') + ); + + return TRUE; + } + + /** + * Callback for reset hub data + * + * @return void + */ + public function resetHubOrganizationData() { + global $wpdb; + + delete_option('h5p_hub_secret'); + + $wpdb->update( + "{$wpdb->prefix}h5p_contents", + array( + 'content_hub_id' => NULL, + 'synced' => NULL, + 'shared' => 0 + ), + array('1' => '1'), // all rows + ); + } } diff --git a/uninstall.php b/uninstall.php index f5500f8..80f63f1 100644 --- a/uninstall.php +++ b/uninstall.php @@ -46,6 +46,15 @@ if (isset($role_info['capabilities']['view_h5p_results'])) { $role->remove_cap('view_h5p_results'); } + if (isset($role_info['capabilities']['manage_h5p_content_hub_registration'])) { + $role->remove_cap('manage_h5p_content_hub_registration'); + } + if (isset($role_info['capabilities']['share_h5p_contents'])) { + $role->remove_cap('share_h5p_contents'); + } + if (isset($role_info['capabilities']['share_others_h5p_contents'])) { + $role->remove_cap('share_others_h5p_contents'); + } } global $wpdb;