diff --git a/package-lock.json b/package-lock.json index 65afedac7..10ebe4637 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1647,7 +1647,7 @@ }, "@polka/url": { "version": "1.0.0-next.24", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", + "resolved": "https://registry.npmjs.org/@polka/tainacan-url-plugin-metadata-type/-/url-1.0.0-next.24.tgz", "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", "dev": true }, diff --git a/src/views/admin/components/metadata-types/metadata-type-helper/class-tainacan-metadata-type-helper.php b/src/views/admin/components/metadata-types/metadata-type-helper/class-tainacan-metadata-type-helper.php index 5984643f7..318d36ff6 100644 --- a/src/views/admin/components/metadata-types/metadata-type-helper/class-tainacan-metadata-type-helper.php +++ b/src/views/admin/components/metadata-types/metadata-type-helper/class-tainacan-metadata-type-helper.php @@ -43,6 +43,7 @@ class Metadata_Type_Helper { $this->Tainacan_Metadata->register_metadata_type('Tainacan\Metadata_Types\User'); $this->Tainacan_Metadata->register_metadata_type('Tainacan\Metadata_Types\Control'); $this->Tainacan_Metadata->register_metadata_type('Tainacan\Metadata_Types\GeoCoordinate'); + $this->Tainacan_Metadata->register_metadata_type('Tainacan\Metadata_Types\URL'); // the priority should see less than on function // `load_admin_page()` of class `Admin` in file /src/views/class-tainacan-admin.php diff --git a/src/views/admin/components/metadata-types/url/FormURL.vue b/src/views/admin/components/metadata-types/url/FormURL.vue new file mode 100644 index 000000000..e1a155805 --- /dev/null +++ b/src/views/admin/components/metadata-types/url/FormURL.vue @@ -0,0 +1,154 @@ + + + diff --git a/src/views/admin/components/metadata-types/url/TainacanURL.vue b/src/views/admin/components/metadata-types/url/TainacanURL.vue new file mode 100644 index 000000000..256a70d81 --- /dev/null +++ b/src/views/admin/components/metadata-types/url/TainacanURL.vue @@ -0,0 +1,87 @@ + + + \ No newline at end of file diff --git a/src/views/admin/components/metadata-types/url/class-tainacan-url.php b/src/views/admin/components/metadata-types/url/class-tainacan-url.php new file mode 100644 index 000000000..4b769cf06 --- /dev/null +++ b/src/views/admin/components/metadata-types/url/class-tainacan-url.php @@ -0,0 +1,209 @@ +set_name( __('URL', 'tainacan') ); + $this->set_description( __('An URL link, possibly with embedded content.', 'tainacan') ); + $this->set_primitive_type('string'); + $this->set_component('tainacan-url'); + $this->set_form_component('tainacan-form-url'); + $this->set_default_options([ + 'link-as-button' => 'no', + 'force-iframe' => 'no', + 'iframe-min-height' => '', + 'iframe-allowfullscreen' => 'no', + 'is-image' => 'no' + ]); + $this->set_preview_template(' +
+
+ +
+
+ '); + } + + /** + * @inheritdoc + */ + public function get_form_labels() { + return [ + 'link-as-button' => [ + 'title' => __( 'Display link as a button', 'tainacan' ), + 'description' => __( 'Style the link to be displayed as a button instead of a simple textual link.', 'tainacan' ), + ], + 'force-iframe' => [ + 'title' => __( 'Force iframe', 'tainacan' ), + 'description' => __( 'Force the URL to be displayed in an iframe in case the content is not embeddable by WordPress.', 'tainacan' ), + ], + 'iframe-min-height' => [ + 'title' => __( 'Forced iframe minimum height', 'tainacan' ), + 'description' => __( 'If forcing the use of an iframe, sets the height attribute, in pixels. Leave it empty to be 100% of the container.', 'tainacan' ), + ], + 'iframe-allowfullscreen' => [ + 'title' => __( 'Allow fullscreen on forced iframe', 'tainacan' ), + 'description' => __( 'If forcing the use of an iframe, allows it to request fullscreen to the browser.', 'tainacan' ), + ], + 'is-image' => [ + 'title' => __( 'Is link to external image', 'tainacan' ), + 'description' => __( 'If you are linking directly to an external image, use this option so it can be properly embedded.', 'tainacan' ), + ] + ]; + } + + /** + * Get the value as a HTML string with links + * @return string + */ + public function get_value_as_html(\Tainacan\Entities\Item_Metadata_Entity $item_metadata) { + + $value = $item_metadata->get_value(); + $link_as_button = $this->get_option('link-as-button') == 'yes'; + $return = ''; + + $return .= $link_as_button ? '
' : ''; + + if ( is_array($value) && $item_metadata->is_multiple() ) { + $total = sizeof($value); + $count = 0; + $prefix = $item_metadata->get_multivalue_prefix(); + $suffix = $item_metadata->get_multivalue_suffix(); + $separator = $item_metadata->get_multivalue_separator(); + + foreach ( $value as $el ) { + if ( !empty($el) ) { + $return .= $prefix; + + $return .= $this->get_single_value_as_html($el); + + $return .= $suffix; + + $count ++; + + if ($count < $total && !$link_as_button) + $return .= $separator; + } + } + + } else { + $return .= $this->get_single_value_as_html($value); + } + $return .= $link_as_button ? '
' : ''; + + return $return; + } + + /** + * Get the a single value as a HTML string with links + * @return string + */ + public function get_single_value_as_html($value) { + global $wp_embed; + + $link_as_button = $this->get_option('link-as-button') == 'yes'; + $return = ''; + + if ($link_as_button) { + $mkstr = preg_replace( + '/\[([^\]]+)\]\(([^\)]+)\)/', + '
\1
', + $value + ); + $return = $this->make_clickable_links($mkstr); + } else { + + // First, we try WordPress autoembed + $embed = $wp_embed->autoembed($value); + + // If it didn't work, it will still ba a URL + if ( esc_url($embed) == esc_url($value) ) { + + // Than we can force the usage of an iframe + if ( $this->get_option('force-iframe') == 'yes' ) { + + // URL points to an image file + if ( $this->get_option('is-image') == 'yes' ) { + $return = sprintf('', $value, $value); + + // URL points to a content that is not an image + } else { + $iframeMininumHeight = '100%'; + + if (!empty($this->get_option('iframe-min-height'))) + $iframeMininumHeight = $this->get_option('iframe-min-height'); + + // Creates an embed with responsive wrapper + $tainacan_embed = \Tainacan\Embed::get_instance(); + $return = $tainacan_embed->add_responsive_wrapper( '' ); + } + + // Or we can leave it as a link + } else { + $mkstr = preg_replace( + '/\[([^\]]+)\]\(([^\)]+)\)/', + '\1', + $value + ); + $return = $this->make_clickable_links($mkstr); + } + + // If the autoembed did work, we pass the responsive wrapper to it + } else { + $tainacan_embed = \Tainacan\Embed::get_instance(); + $return = $tainacan_embed->add_responsive_wrapper($embed); + } + } + + return $return; + } + + /** + * Checks if the value passed is a valid URL or markdown link + * @return boolean + */ + public function validate(\Tainacan\Entities\Item_Metadata_Entity $item_metadata) { + $value = $item_metadata->get_value(); + //$reg_mrkd = '~\[(.+)\]\(([^ ]+)?\)~i'; + $reg_url = '~^((www\.|http:\/\/www\.|http:\/\/|https:\/\/www\.|https:\/\/|ftp:\/\/www\.|ftp:\/\/|ftps:\/\/www\.|ftps:\/\/)[^"<\s]+)(?![^<>]*>|[^"]*?<\/a)$~i'; + $reg_full = '~\[(.+)\]\((((www\.|http:\/\/www\.|http:\/\/|https:\/\/www\.|https:\/\/|ftp:\/\/www\.|ftp:\/\/|ftps:\/\/www\.|ftps:\/\/)[^"<\s]+)(?![^<>]*>|[^"]*?<\/a))?\)~i'; + + // Multivalued metadata -------------- + if ( is_array($value) ) { + foreach ($value as $url_value) { + + // Empty strings are valid + if ( !empty($url_value) ) { + + // If this seems to be a markdown link, we check if the url inside it is ok as well + if ( !preg_match($reg_url, $url_value) && !preg_match($reg_full, $url_value) ) { + $this->add_error( sprintf( __('"%s" is invalid. Please provide a valid, full URL or a Markdown link in the form of [label](url).', 'tainacan'), $url_value ) ); + return false; + } + } + } + return true; + } + + // Single valued metadata -------------- + // Empty strings are valid + if ( !empty($value) ) { + + // If this seems to be a markdown link, we check if the url inside it is ok as well + if ( !preg_match($reg_url, $value) && !preg_match($reg_full, $value) ) { + $this->add_error( sprintf( __('"%s" is invalid. Please provide a valid, full URL or a Markdown link in the form of [label](url).', 'tainacan'), $value ) ); + return false; + } + } + return true; + } + +} +?> diff --git a/src/views/admin/js/admin-main.js b/src/views/admin/js/admin-main.js index cbbcf3bc0..b6191093d 100644 --- a/src/views/admin/js/admin-main.js +++ b/src/views/admin/js/admin-main.js @@ -44,6 +44,7 @@ import TainacanTaxonomy from '../components/metadata-types/taxonomy/TainacanTaxo import TainacanCompound from '../components/metadata-types/compound/TainacanCompound.vue'; import TainacanUser from '../components/metadata-types/user/TainacanUser.vue'; import TainacanGeoCoordinate from '../components/metadata-types/geocoordinate/TainacanGeoCoordinate.vue' +import TainacanURL from '../components/metadata-types/url/TainacanURL.vue'; import FormText from '../components/metadata-types/text/FormText.vue'; import FormTextarea from '../components/metadata-types/textarea/FormTextarea.vue'; @@ -53,6 +54,7 @@ import FormSelectbox from '../components/metadata-types/selectbox/FormSelectbox. import FormNumeric from '../components/metadata-types/numeric/FormNumeric.vue'; import FormUser from '../components/metadata-types/user/FormUser.vue'; import FormGeoCoordinate from '../components/metadata-types/geocoordinate/FormGeoCoordinate.vue'; +import FormURL from '../components/metadata-types/url/FormURL.vue'; // Term edition form must be imported here so that it is not necessary on item-submission bundle import TermEditionForm from '../components/edition/term-edition-form.vue'; @@ -231,6 +233,7 @@ export default (element) => { app.component('tainacan-compound', TainacanCompound); app.component('tainacan-user', TainacanUser); app.component('tainacan-geocoordinate', TainacanGeoCoordinate); + app.component('tainacan-url', TainacanURL); /* Metadata Option forms */ @@ -243,6 +246,7 @@ export default (element) => { app.component('tainacan-form-user', FormUser); app.component('term-edition-form', TermEditionForm); app.component('tainacan-form-geocoordinate', FormGeoCoordinate); + app.component('tainacan-form-url', FormURL); /* Filter Metadata Option forms */ app.component('tainacan-filter-form-numeric', FormFilterNumeric); diff --git a/src/views/admin/scss/tainacan-admin.scss b/src/views/admin/scss/tainacan-admin.scss index f41f24d7e..b3ff64015 100644 --- a/src/views/admin/scss/tainacan-admin.scss +++ b/src/views/admin/scss/tainacan-admin.scss @@ -304,6 +304,24 @@ html.is-clipped .page-container-small:not(.is-filters-menu-open) { .taginput-container .input:hover { border: none !important; } +.metadata-type-ainacan_url_plugin_metadata_type .multivalue-separator, +.metadata-type-tainacan_url_plugin_metadata_type .multivalue-separator { + display: block; + max-height: 1px; + width: 80px; + background: var(--tainacan-gray3, #a5a5a5); + content: none; + color: transparent !important; + margin: 1em auto 1em 0 !important; +} +.metadata-type-ainacan_url_plugin_metadata_type .wp-block-buttons, +.metadata-type-tainacan_url_plugin_metadata_type .wp-block-buttons { + display: flex !important; +} +.metadata-type-ainacan_url_plugin_metadata_type .wp-block-buttons>.wp-block-button, +.metadata-type-tainacan_url_plugin_metadata_type .wp-block-buttons>.wp-block-button { + width: auto !important; +} // Buefy notices (toast, snackbar...) .notices { diff --git a/src/views/gutenberg-blocks/blocks/item-submission-form/theme.js b/src/views/gutenberg-blocks/blocks/item-submission-form/theme.js index 91bde8082..c5e3301c4 100644 --- a/src/views/gutenberg-blocks/blocks/item-submission-form/theme.js +++ b/src/views/gutenberg-blocks/blocks/item-submission-form/theme.js @@ -38,6 +38,7 @@ import TainacanTaxonomy from '../../../admin/components/metadata-types/taxonomy/ import TainacanCompound from '../../../admin/components/metadata-types/compound/TainacanCompound.vue'; import TainacanUser from '../../../admin/components/metadata-types/user/TainacanUser.vue'; import TainacanGeoCoordinate from '../../../admin/components/metadata-types/geocoordinate/TainacanGeoCoordinate.vue'; +import TainacanURL from '../../../admin/components/metadata-types/url/TainacanURL.vue'; // Main components import ItemSubmissionForm from './theme.vue'; @@ -176,6 +177,7 @@ export default (element) => { VueItemSubmission.component('tainacan-compound', TainacanCompound); VueItemSubmission.component('tainacan-user', TainacanUser); VueItemSubmission.component('tainacan-geocoordinate', TainacanGeoCoordinate); + VueItemSubmission.component('tainacan-url', TainacanURL); /* Others */ VueItemSubmission.component('tainacan-form-item', TainacanFormItem); diff --git a/src/views/tainacan-i18n.php b/src/views/tainacan-i18n.php index 0ebe2e342..33ff34bd8 100644 --- a/src/views/tainacan-i18n.php +++ b/src/views/tainacan-i18n.php @@ -706,6 +706,7 @@ return apply_filters( 'tainacan-i18n', [ 'label_item_edition_form_options' => __( 'Item edition form options', 'tainacan' ), 'label_item_submission_options' => __( 'Item submission options', 'tainacan' ), 'label_metadata_related_features' => __( 'Metadata related features', 'tainacan' ), + 'label_preview' => __( 'Preview', 'tainacan' ), // Instructions. More complex sentences to guide user and placeholders 'instruction_delete_selected_collections' => __( 'Delete selected collections', 'tainacan' ),