Merge branch 'develop' of https://github.com/tainacan/tainacan into develop

This commit is contained in:
weryques 2018-03-05 08:57:56 -03:00
commit 6063fd4160
39 changed files with 1512 additions and 1032 deletions

3
.gitignore vendored
View File

@ -18,4 +18,5 @@ last-composer-build.md5
last-package-build.md5
src/admin/scss/.sass-cache
src/assets/css/tainacan-admin.css
src/assets/css/tainacan-admin.css.map
src/assets/css/tainacan-admin.css.map
cypress

2
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,2 @@
{
}

View File

@ -1,2 +1,4 @@
# the destination folder, inside the plugins folder of some WordPress installation
destination=~/devel/wordpress/wp-content/plugins/tainacan
destination=~/devel/wordpress/wp-content/plugins/tainacan
wp_base_dir=~/devel/wordpress
wp_url=http://localhost/wp

1
cypress.json Normal file
View File

@ -0,0 +1 @@
{}

1646
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@
"babel-preset-stage-2": "^6.24.1",
"cross-env": "^3.0.0",
"css-loader": "^0.25.0",
"cypress": "^2.1.0",
"element-theme-chalk": "^2.1.0",
"file-loader": "^0.9.0",
"postcss-loader": "^2.1.0",

41
run-tests.sh Executable file
View File

@ -0,0 +1,41 @@
#!/bin/bash
source build-config.cfg
#./build.sh
plugin_name=`basename $destination`
test_db_prefix='wp_tainacan_test_'
################## set up cypress environment
# replace table prefix in wp_config
echo "Changing DB prefix in wp_config..."
sed -i s/"require_once(ABSPATH . 'wp-settings.php');"/"\$table_prefix = '$test_db_prefix';\nrequire_once(ABSPATH . 'wp-settings.php');"/ $wp_base_dir/wp-config.php
# install WordPress
cd $wp_base_dir
echo "Installing WordPress..."
wp core install --url=$wp_url --title=Test --admin_user=admin --admin_password=admin --admin_email=admin@admin.com
# Activate Tainacan
wp plugin activate $plugin_name
# back to tainacan dev directory
cd -
#npx cypress run --env host=$wp_url,baseUrl=$wp_url
################## teardown cypress environment
# remove WordPress
echo "Removing WordPress..."
cd $wp_base_dir
wp db query "DROP TABLES $(wp db tables | paste -s -d, -);"
# recover table prefix in wp_config
echo "Restoring DB prefix in wp_config..."
sed -i s/"\$table_prefix = '$test_db_prefix';"/""/ $wp_base_dir/wp-config.php

View File

@ -85,7 +85,8 @@ class Admin {
}
$settings = [
'root' => esc_url_raw( rest_url() ).'tainacan/v2',
'root' => esc_url_raw( rest_url() ).'tainacan/v2',
'root_wp_api' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'components' => $components,
'i18n' => $tainacan_admin_i18n,

View File

@ -8,10 +8,10 @@
v-model="collectionsPerPage"
@input="onChangeCollectionsPerPage"
:disabled="collections.length <= 0">
<option value="2">2 {{ $i18n.get('label_per_page') }}</option>
<option value="10">10 {{ $i18n.get('label_per_page') }}</option>
<option value="15">15 {{ $i18n.get('label_per_page') }}</option>
<option value="20">20 {{ $i18n.get('label_per_page') }}</option>
<option value="12">12 {{ $i18n.get('label_per_page') }}</option>
<option value="24">24 {{ $i18n.get('label_per_page') }}</option>
<option value="48">48 {{ $i18n.get('label_per_page') }}</option>
<option value="96">96 {{ $i18n.get('label_per_page') }}</option>
</b-select>
</b-field>
@ -52,7 +52,7 @@
</router-link>
</b-table-column>
<b-table-column tabindex="0" label="Ações" width="110" :aria-label="$i18n.get('label_ações')">
<b-table-column tabindex="0" label="Ações" width="80" :aria-label="$i18n.get('label_ações')">
<!-- <a id="button-view" :aria-label="$i18n.get('label_button_view')" @click.prevent.stop="goToCollectionPage(props.row.id)"><b-icon icon="eye"></a> -->
<a id="button-edit" :aria-label="$i18n.get('label_button_edit')" @click.prevent.stop="goToCollectionEditPage(props.row.id)"><b-icon icon="pencil"></a>
<a id="button-delete" :aria-label="$i18n.get('label_button_delete')" @click.prevent.stop="deleteOneCollection(props.row.id)"><b-icon icon="delete"></a>
@ -94,7 +94,7 @@ export default {
isLoading: false,
totalCollections: 0,
page: 1,
collectionsPerPage: 2
collectionsPerPage: 12
}
},
methods: {
@ -107,13 +107,13 @@ export default {
]),
deleteOneCollection(collectionId) {
this.$dialog.confirm({
message: this.$i18n.get('info_warning_collection_deleted'),
message: this.$i18n.get('info_warning_collection_delete'),
onConfirm: () => {
this.deleteCollection(collectionId).then(() => {
this.loadCollections();
this.$toast.open({
duration: 3000,
message: this.$i18n.get('info_collection_delete'),
message: this.$i18n.get('info_collection_deleted'),
position: 'is-bottom',
type: 'is-secondary',
queue: true
@ -174,6 +174,7 @@ export default {
},
onChangeCollectionsPerPage(value) {
this.collectionsPerPage = value;
this.$userPrefs.set('items_per_page', value);
this.loadCollections();
},
onPageChange(page) {
@ -197,6 +198,15 @@ export default {
return this.getCollections();
}
},
created() {
this.$userPrefs.get('items_per_page')
.then((value) => {
this.collectionsPerPage = value;
})
.catch((error) => {
this.$userPrefs.set('items_per_page', 12);
});
},
mounted(){
this.loadCollections();
}

View File

@ -24,10 +24,10 @@
v-model="itemsPerPage"
@input="onChangeItemsPerPage"
:disabled="items.length <= 0">
<option value="2">2 {{ $i18n.get('label_per_page') }}</option>
<option value="10">10 {{ $i18n.get('label_per_page') }}</option>
<option value="15">15 {{ $i18n.get('label_per_page') }}</option>
<option value="20">20 {{ $i18n.get('label_per_page') }}</option>
<option value="12">12 {{ $i18n.get('label_per_page') }}</option>
<option value="24">24 {{ $i18n.get('label_per_page') }}</option>
<option value="48">48 {{ $i18n.get('label_per_page') }}</option>
<option value="96">96 {{ $i18n.get('label_per_page') }}</option>
</b-select>
</b-field>
@ -110,7 +110,7 @@ export default {
isLoading: false,
totalItems: 0,
page: 1,
itemsPerPage: 2
itemsPerPage: 12
}
},
props: {
@ -192,6 +192,7 @@ export default {
},
onChangeItemsPerPage(value) {
this.itemsPerPage = value;
this.$userPrefs.set('items_per_page', value);
this.loadItems();
},
goToItemPage(itemId) {
@ -221,6 +222,15 @@ export default {
return this.getItems();
}
},
created() {
this.$userPrefs.get('items_per_page')
.then((value) => {
this.itemsPerPage = value;
})
.catch((error) => {
this.$userPrefs.set('items_per_page', 12);
});
},
mounted(){
this.loadItems();
this.fetchFields(this.collectionId).then((res) => {
@ -237,7 +247,9 @@ export default {
}
</script>
<style scoped>
<style lang="scss" scoped>
@import "../scss/_variables.scss";
.table-thumb {
max-height: 55px !important;

View File

@ -65,14 +65,14 @@ export default {
@import "../scss/_variables.scss";
#primary-menu {
background-color: $primary-dark;
background-color: $secondary;
padding: 0px;
-webkit-transition: max-width 0.2s linear; /* Safari */
transition: max-width 0.2s linear;
max-width: 222px;
.menu-header {
background-color: $primary-darker;
background-color: rgba(0,0,0,0.1);
height: 62px;
a { padding: 1em 2.5em }
.icon {
@ -92,7 +92,7 @@ export default {
}
.separator {
height: 2px;
background-color: $primary-darker;
background-color: $separator-color;
width: 100%;
}
li{
@ -107,12 +107,12 @@ export default {
transition: padding 0.2s linear;
}
a:hover {
background-color: $primary-light;
color: $secondary
background-color: $primary;
color: $tertiary
}
a.is-active {
background-color: $primary;
color: $secondary;
color: $tertiary;
}
.menu-text {
padding-left: 0.7em;
@ -137,7 +137,10 @@ export default {
opacity: 0;
}
}
a{ padding: 1em 0.8em; }
a {
padding: 1em 0.8em;
color: rgba(255,255,255,0.4);
}
.menu-text {
visibility: hidden;
opacity: 0;

View File

@ -78,7 +78,7 @@ export default {
max-width: 180px;
.menu-header {
background-color: $primary-dark;
background-color: rgba(0,0,0,0.1);
height: 62px;
a { padding: 1em 1.2em; }
.icon {
@ -109,12 +109,12 @@ export default {
transition: padding 0.3s linear;
}
a:hover {
background-color: $primary-lighter;
color: $secondary
background-color: rgba(255,255,255,0.4);
color: $tertiary;
}
a.is-active {
background-color: $primary-light;
color: $secondary;
background-color: rgba(255,255,255,0.4);
color: $tertiary;
}
.menu-text {
padding-left: 0.7em;
@ -132,7 +132,7 @@ export default {
.menu-header { display: none;
}
.menu-text {
display: none !important;
padding-left: 0.3em !important;
}
ul {
display: flex;

View File

@ -18,7 +18,7 @@ import AdminPage from '../admin.vue'
import draggable from 'vuedraggable'
import store from '../../js/store/store'
import router from './router'
import { I18NPlugin, RouterHelperPlugin } from './utilities';
import { I18NPlugin, UserPrefsPlugin, RouterHelperPlugin } from './utilities';
// Configure and Register Plugins
router.beforeEach((to, from, next) => {
@ -26,6 +26,7 @@ router.beforeEach((to, from, next) => {
next()
});
Vue.use(I18NPlugin);
Vue.use(UserPrefsPlugin);
Vue.use(RouterHelperPlugin);
Vue.use(Buefy);

View File

@ -1,4 +1,11 @@
import qs from 'qs';
import axios from 'axios';
const wpApi = axios.create({
baseURL: tainacan_plugin.root_wp_api
});
wpApi.defaults.headers.common['X-WP-Nonce'] = tainacan_plugin.nonce;
// I18N PLUGIN - Allows access to Wordpress translation file.
export const I18NPlugin = {};
@ -13,6 +20,43 @@ I18NPlugin.install = function (Vue, options = {}) {
}
// USER PREFERENCES - Used to save key-value information for user settings of plugin
export const UserPrefsPlugin = {};
UserPrefsPlugin.install = function (Vue, options = {}) {
Vue.prototype.$userPrefs = {
get(key) {
return new Promise(( resolve, reject ) => {
wpApi.get('/wp/v2/users/me/')
.then( res => {
if (res.data.meta.hasOwnProperty(key))
resolve( res.data.key );
else
reject( { message: 'Key does not exists in user preference.', value: false} );
})
.catch(error => {
reject( { message: error, value: false});
});
});
},
set(metakey, value) {
let data = {
'meta': { metakey: value }
};
return new Promise(( resolve, reject ) => {
wpApi.post('/wp/v2/users/me/?context=edit&' + qs.stringify(data))
.then( res => {
resolve( res.data );
})
.catch(error => {
reject( error );
});
});
}
}
}
// ROUTER HELPER PLUGIN - Allows easy access to URL paths for entities
export const RouterHelperPlugin = {};
RouterHelperPlugin.install = function (Vue, options = {}) {

View File

@ -1,7 +1,7 @@
<template>
<div>
<h1>Collections Page</h1>
<router-link tag="button" class="button is-primary"
<router-link tag="button" class="button is-secondary"
:to="{ path: $routerHelper.getNewCollectionPath() }">
{{ $i18n.get('new') + ' ' + $i18n.get('collection') }}
</router-link>

View File

@ -1,7 +1,7 @@
<template>
<div>
<h1>Items Page</h1>
<router-link tag="button" class="button is-primary"
<router-link tag="button" class="button is-secondary"
:to="{ path: $routerHelper.getNewItemPath(collectionId) }">
{{ $i18n.get('new') + ' ' + $i18n.get('item') }}
</router-link>

View File

@ -2,24 +2,35 @@
@import "../../../node_modules/bulma/sass/utilities/_all.sass";
// Tainacan custom colors
$primary: #7DB9C3;// #25a189;
$primary: #2cb4c1;// #25a189;
$primary-invert: findColorInvert($primary);
$secondary: #1F2F56;
$secondary-invert: findColorInvert($primary);
$secondary: #298596;
$secondary-invert: findColorInvert($secondary);
$tertiary: #1f2f56;
$tertiary-invert: findColorInvert($tertiary);
$primary-light:#A5CDD7;
$primary-lighter: lighten($primary-light, 15%);
$primary-dark: #55A0AF;
$primary-darker: darken($primary-dark, 5%);
$separator-color: #2a6e77;
$gray: #898d8f;
$gray-invert: findColorInvert($gray);
$gray-light: #898d8f;
$gray-light-invert: findColorInvert($gray-light);
// Setup $colors to use as bulma classes
$colors: (
"white": ($white, $black),
"black": ($black, $white),
"gray": ($gray, $gray-invert),
"gray-light": ($gray-light, $gray-light-invert),
"light": ($light, $light-invert),
"dark": ($dark, $dark-invert),
"primary": ($primary, $primary-invert),
"secondary": ($secondary, $secondary-invert),
"tertiary": ($secondary, $secondary-invert),
"info": ($info, $info-invert),
"success": ($success, $success-invert),
"warning": ($warning, $warning-invert),

View File

@ -6,6 +6,11 @@ $link: $primary;
$link-invert: $primary-invert;
$link-focus-border: $primary;
// Table
$table-head-cell-color: $gray-light;
$table-row-active-color: #e5e5e5;
$table-head-cell-border-width: 0 0 1px;
// Bulma's modal (needs to be greather than taincan-admin-app
$modal-z: 9999999;
@ -14,6 +19,9 @@ $modal-z: 9999999;
@import "../../../node_modules/bulma/bulma.sass";
@import "../../../node_modules/buefy/src/scss/buefy.scss";
// Roboto font
$family-sans-serif: 'Roboto', sans-serif;
// Clears wordpress content
body.tainacan-admin-page #adminmenumain, body.tainacan-admin-page #wpfooter, body.tainacan-admin-page #wp-auth-check-wrap {
display: none;
@ -31,6 +39,7 @@ body.tainacan-admin-page #adminmenumain, body.tainacan-admin-page #wpfooter, bod
overflow-y: auto;
height: 100%;
margin: 0px !important;
font-family: $family-sans-serif;
}
/* Rules for sizing the icon. */
@ -49,5 +58,5 @@ body.tainacan-admin-page #adminmenumain, body.tainacan-admin-page #wpfooter, bod
// Buefy notices (toast)
.notices {
z-index: 99999999 !important;
z-index: 99999999 !important;
}

View File

@ -370,14 +370,14 @@ class Field extends Entity {
$fto = $this->get_field_type_object();
if (is_object($fto)) {
$is_valid = $fto->validate_options($this->get_field_type_options());
$is_valid = $fto->validate_options($this);
}
if (true === $is_valid)
return true;
if (!is_array($is_valid))
throw new Exception("Return of validate_options field type method should be an Array in case of error");
throw new \Exception("Return of validate_options field type method should be an Array in case of error");
foreach ($is_valid as $field => $message) {
$this->add_error($field, $message);

View File

@ -0,0 +1,146 @@
<?php
namespace Tainacan\Field_Types;
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
/**
* Class TainacanFieldType
*/
class Category extends Field_Type {
function __construct(){
// call field type constructor
parent::__construct();
parent::set_primitive_type('term');
$this->set_default_options([
'allow_new_terms' => false
]);
// TODO: Set component depending on options. If multiple is checkbox. if single, select. etc.
$this->component = 'tainacan-category';
}
/**
* @param $itemMetadata \Tainacan\Entities\Item_Metadata_Entity The instace of the entity itemMetadata
* @return string
*/
public function render( $itemMetadata ){
$options = ( isset( $this->options['options'] ) ) ? $this->options['options'] : '';
return '<tainacan-selectbox
options="' . $options . '"
field_id ="'.$itemMetadata->get_field()->get_id().'"
item_id="'.$itemMetadata->get_item()->get_id().'"
value=\''.json_encode( $itemMetadata->get_value() ).'\'
name="'.$itemMetadata->get_field()->get_name().'"></tainacan-selectbox>';
}
/**
* generate the fields for this field type
*/
public function form(){
global $Tainacan_Taxonomies;
$taxonomies = $Tainacan_Taxonomies->fetch([], 'OBJECT')
// TODO: form incomplete and not tested
?>
<tr>
<td>
<label><?php echo __('Category','tainacan'); ?></label><br/>
<small><?php echo __('Select the category','tainacan'); ?></small>
</td>
<td>
<select name="taxonomy_id">
<?php foreach ($taxonomies as $tax): ?>
<option value="<?php echo $tax->get_db_identifier(); ?>" <?php selected($this->get_option('taxonomy_id'), $tax->get_db_identifier()); ?> ><?php echo $tax->get_name(); ?></option>
<?php endforeach; ?>
</select>
</td>
<td>
<label><?php echo __('Allow creation of new terms','tainacan'); ?></label><br/>
<small><?php echo __('If checked, users may create new terms for this category, otherwise they can only selected from existing terms.','tainacan'); ?></small>
</td>
<td>
<input type="checkbox" name="allow_new_terms" <?php checked(true, $this->get_option('allow_new_terms')); ?> >
<label>Allow</label>
</td>
</tr>
<?php
}
public function validate_options(\Tainacan\Entities\Field $field) {
if ( !in_array($field->get_status(), apply_filters('tainacan-status-require-validation', ['publish','future','private'])) )
return true;
if (empty($this->get_option('taxonomy_id')))
return ['taxonomy_id' => __('Please select a category', 'tainacan')];
global $Tainacan_Fields;
$category_fields = $Tainacan_Fields->fetch([
'collection_id' => $field->get_collection_id(),
'field_type' => 'Tainacan\\Field_Types\\Category'
], 'OBJECT');
$category_fields = array_map(function ($field) {
$fto = $field->get_field_type_object();
return $fto->get_option('taxonomy_id');
}, $category_fields);
if (in_array($this->get_option('taxonomy_id'), $category_fields)) {
return ['taxonomy_id' => __('You can not have 2 Category Fields using the same category in a collection', 'tainacan')];
}
return true;
}
/**
* Validate item based on field type categories options
*
* @param TainacanEntitiesItem_Metadata_Entity $item_metadata
* @return bool Valid or not
*/
public function validate(\Tainacan\Entities\Item_Metadata_Entity $item_metadata) {
$item = $item_metadata->get_item();
$field = $item_metadata->get_field();
if ( !in_array($item->get_status(), apply_filters('tainacan-status-require-validation', ['publish','future','private'])) )
return true;
$valid = true;
if (false === $this->get_option('allow_new_terms')) {
$terms = $item_metadata->get_value();
if (false === $terms)
return true;
if (!is_array($terms))
$terms = array($terms);
foreach ($terms as $term) {
if (is_object($term) && $term instanceof \WP_Term) {
$term = $term->term_id;
}
if (!term_exists($term)) {
$valid = false;
break;
}
}
}
return $valid;
}
}

View File

@ -9,8 +9,32 @@ defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
*/
abstract class Field_Type {
/**
* Indicates the type of variable that this field type handles.
*
* This is used to relate Field types and filter types, so we know which filter types
* will be available to be used for each field based on its Field Type
*
* For instance, the Filter Type "input text" may be used to search in any field that has
* a Field Type with a string primitive type.
*
* @var string
*/
private $primitive_type;
public $options;
/**
* Array of options spececific to this field type. Stored in field_type_options property of the Field object
* @var Array
*/
public $options = [];
/**
* The default values for the field type options array
* @var Array
*/
public $default_options = [];
public $errors;
/**
@ -70,6 +94,32 @@ abstract class Field_Type {
public function set_options( $options ){
$this->options = ( is_array( $options ) ) ? $options : unserialize( $options );
}
public function set_default_options(Array $options) {
$this->default_options = $options;
}
/**
* Gets the options for this field types, including default values for options
* that were not set yet.
* @return Array Fielt type options
*/
public function get_options() {
return array_merge($this->default_options, $this->options);
}
/**
* Gets one option from the options array.
*
* Checks if option exist or if it have a default value. Otherwise return an empty string
*
* @param string $key the desired option
* @return mixed the option value, the default value or an empty string
*/
public function get_option($key) {
$options = $this->get_options();
return isset($options[$key]) ? $options[$key] : '';
}
/**
* generate the fields for this field type
@ -95,10 +145,10 @@ abstract class Field_Type {
*
* This method should be declared by each field type sub classes
*
* @param Array $options Options to be saved as field_type_optins
* @param \Tainacan\Entities\Field $field The field object that is beeing validated
* @return true|Array True if optinos are valid. If invalid, returns an array where keys are the field keys and values are error messages.
*/
public function validate_options(Array $options) {
public function validate_options(\Tainacan\Entities\Field $field) {
return true;
}

View File

@ -98,9 +98,9 @@ class Relationship extends Field_Type {
<?php
}
public function validate_options(Array $options) {
public function validate_options(\Tainacan\Entities\Field $field) {
// TODO: This is just a sample validation to test validation workflow for field types. Must redo it
if (isset($options['collection_id']) && !is_numeric($options['collection_id'])) {
if (!empty($this->get_option('collection_id')) && !is_numeric($this->get_option('collection_id'))) {
return [
'collection_id' => 'Collection ID invalid'
];

View File

@ -0,0 +1,114 @@
<template>
<div class="block">
<b-autocomplete
rounded
icon="magnify"
:id="id"
v-model="selected"
:data="options"
@input="search"
:loading="loading"
field="label"
@select="option => setResults(option) ">
</b-autocomplete>
</div>
</template>
<script>
import axios from '../../../js/axios/axios'
export default {
created(){
this.collection = ( this.collection_id ) ? this.collection_id : this.filter.collection_id;
this.type = ( this.filter_type ) ? this.filter_type : this.filter.field.field_type;
},
data(){
return {
results:'',
selected:'',
options: [],
isLoading: false,
type: '',
collection: '',
selected: '',
}
},
props: {
filter: {
type: Object // concentrate all attributes field id and type
},
field_id: [Number], // not required, but overrides the filter field id if is set
collection_id: [Number], // not required, but overrides the filter field id if is set
filter_type: [String], // not required, but overrides the filter field type if is set
id: ''
},
methods: {
setResults(option){
if(!option)
return;
this.results = option.value;
this.onSelect()
},
onSelect(){
let filter = null;
if ( this.type ) {
filter = 'term';
} else {
filter = 'selectbox';
}
this.$emit('input', {
filter: filter,
field_id: ( this.field_id ) ? this.field_id : this.filter.field,
collection_id: ( this.collection_id ) ? this.collection_id : this.filter.collection_id,
value: this.results
});
},
search( query ){
let promise = null;
this.options = [];
if ( this.type === 'Tainacan\Field_types\Relationship' ) {
let collectionTarget = ( this.filter && this.filter.field.field_type_options.collection_id ) ?
this.filter.field.field_type_options.collection_id : this.collection_id;
promise = this.getValuesRelationship( collectionTarget, query );
} else if ( this.type === 'Tainacan\Field_types\Category' ) {
let collectionTarget = ( this.filter && this.filter.field.field_type_options.taxonomy ) ?
this.filter.field.field_type_options.taxonomy : this.taxonomy;
promise = this.getValuesCategory( collectionTarget, query );
} else {
promise = this.getValuesPlainText( this.filter.field.id, query );
}
promise.then( data => {
this.isLoading = false;
})
.catch( error => {
console.log('error select', error );
this.isLoading = false;
});
},
getValuesPlainText( field_id ){
// TODO: get values from items
},
getValuesCategory( taxonomy ){
// TODO: get taxonomy terms
},
getValuesRelationship( collectionTarget, search ){
return axios.get( '/collection/' + collectionTarget + '/items?s=' + search )
.then( res => {
for (let item of res.data) {
this.options.push({ label: item.title, value: item.id })
}
})
.catch(error => {
console.log(error);
});
},
}
}
</script>

View File

@ -0,0 +1,26 @@
<?php
namespace Tainacan\Filter_Types;
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
/**
* Class TainacanFieldType
*/
class Autocomplete extends Filter_Type {
function __construct(){
parent::set_supported_types(['string','Tainacan\Field_Types\Relationship']);
$this->component = 'tainacan-filter-autocomplete';
}
/**
* @param $filter
* @return string
*/
public function render( $filter ){
return '<tainacan-filter-autocomplete name="'.$filter->get_name().'"
collection_id="'.$filter->get_collection_id().'"
field_id="'.$filter->get_field()->get_id().'"></tainacan-filter-autocomplete>';
}
}

View File

@ -7,11 +7,11 @@ defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
/**
* Class TainacanFieldType
*/
class Range extends Filter_Type {
class Custom_Interval extends Filter_Type {
function __construct(){
parent::set_supported_types(['float','date']);
$this->component = 'tainacan-filter-range';
$this->component = 'tainacan-filter-custom-interval';
}
/**
@ -20,10 +20,10 @@ class Range extends Filter_Type {
*/
public function render( $filter ){
$type = ( $filter->get_field()->get_field_type() === 'Tainacan\Field_Types\Date' ) ? 'date' : 'numeric';
return '<tainacan-filter-range
return '<tainacan-filter-custom-interval
name="'.$filter->get_name().'"
typeRange="'.$type.'"
collection_id="'.$filter->get_collection_id().'"
field_id="'.$filter->get_field()->get_id().'"></tainacan-filter-range>';
field_id="'.$filter->get_field()->get_id().'"></tainacan-filter-custom-interval>';
}
}

View File

@ -1,20 +1,18 @@
<template>
<div class="block">
<b-field>
<b-select
:id = "id"
:laoding = "isLoading"
v-model = "selected"
@input = "onSelect()"
expanded>
<option
v-for="option,index in options"
:key="index"
:label="option.label"
:value="option.value"
border>{{ option.label }}</option>
</b-select>
</b-field>
<b-select
:id = "id"
:laoding = "isLoading"
v-model = "selected"
@input = "onSelect()"
expanded>
<option
v-for="option,index in options"
:key="index"
:label="option.label"
:value="option.value"
border>{{ option.label }}</option>
</b-select>
</div>
</template>
@ -24,6 +22,7 @@
export default {
created(){
this.collection = ( this.collection_id ) ? this.collection_id : this.filter.collection_id;
this.type = ( this.filter_type ) ? this.filter_type : this.filter.field.field_type;
this.loadOptions();
},
data(){

View File

@ -286,7 +286,9 @@ class Fields extends Repository {
'posts_per_page' => -1,
'post_status' => 'publish'
], $args);
$args = $this->parse_fetch_args($args);
$args['post_type'] = Entities\Field::get_post_type();
$wp_query = new \WP_Query($args);

View File

@ -11,26 +11,29 @@ class Item_Metadata extends Repository {
public function insert($item_metadata) {
$unique = !$item_metadata->is_multiple();
$field_type = $item_metadata->get_field()->get_field_type_object();
if ($field_type->core) {
$this->save_core_field_value($item_metadata);
} elseif ($field_type->get_primitive_type() == 'term') {
$this->save_terms_field_value($item_metadata);
} else {
if ($unique) {
update_post_meta($item_metadata->item->get_id(), $item_metadata->field->get_id(), wp_slash( $item_metadata->get_value() ) );
} else {
delete_post_meta($item_metadata->item->get_id(), $item_metadata->field->get_id());
if (is_array($item_metadata->get_value())){
$values = $item_metadata->get_value();
if ($unique) {
$field_type = $item_metadata->get_field()->get_field_type_object();
if ($field_type->core) {
$this->save_core_field_value($item_metadata);
} else {
update_post_meta($item_metadata->item->get_id(), $item_metadata->field->get_id(), wp_slash( $item_metadata->get_value() ) );
}
} else {
delete_post_meta($item_metadata->item->get_id(), $item_metadata->field->get_id());
if (is_array($item_metadata->get_value())){
$values = $item_metadata->get_value();
foreach ($values as $value){
add_post_meta($item_metadata->item->get_id(), $item_metadata->field->get_id(), wp_slash( $value ));
}
}
}
foreach ($values as $value){
add_post_meta($item_metadata->item->get_id(), $item_metadata->field->get_id(), wp_slash( $value ));
}
}
}
}
do_action('tainacan-insert', $item_metadata);
do_action('tainacan-insert-Item_Metadata_Entity', $item_metadata);
@ -60,6 +63,14 @@ class Item_Metadata extends Repository {
}
}
}
public function save_terms_field_value($item_metadata) {
$field_type = $item_metadata->get_field()->get_field_type_object();
if ($field_type->get_primitive_type() == 'term') {
$new_terms = $item_metadata->get_value();
wp_set_object_terms($item_metadata->get_item()->get_id(), $new_terms, $field_type->get_option('taxonomy_id'));
}
}
/**
* Fetch Item Field objects related to an Item
@ -110,7 +121,16 @@ class Item_Metadata extends Repository {
$get_method = 'get_' . $field_type->related_mapped_prop;
return $item->$get_method();
} elseif ($field_type->get_primitive_type() == 'term') {
$terms = wp_get_object_terms($item_metadata->get_item()->get_id(), $field_type->get_option('taxonomy_id'));
if ($unique)
$terms = reset($terms);
return $terms;
} else {
return get_post_meta($item_metadata->item->get_id(), $item_metadata->field->get_id(), $unique);
}

View File

@ -118,7 +118,7 @@ abstract class Repository {
return false;
if ($map[$prop]['map'] == 'meta') {
update_post_meta($obj->get_id(), $prop, wp_slash( $obj->get_mapped_property($prop) ));
update_post_meta($obj->get_id(), $prop, $this->maybe_add_slashes( $obj->get_mapped_property($prop) ));
} elseif($map[$prop]['map'] == 'meta_multi') {
$values = $obj->get_mapped_property($prop);
@ -126,11 +126,18 @@ abstract class Repository {
if (is_array($values)){
foreach ($values as $value){
add_post_meta($obj->get_id(), $prop, wp_slash( $value ));
add_post_meta($obj->get_id(), $prop, $this->maybe_add_slashes( $value ));
}
}
}
}
function maybe_add_slashes($value) {
if (is_string($value) && strpos($value, '\\') !== false) {
return wp_slash($value);
}
return $value;
}
/**
* Prepare the output for the fetch() methods.

View File

@ -88,12 +88,13 @@ $Tainacan_Fields->register_field_type('Tainacan\Field_Types\Numeric');
$Tainacan_Fields->register_field_type('Tainacan\Field_Types\Selectbox');
$Tainacan_Fields->register_field_type('Tainacan\Field_Types\Relationship');
$Tainacan_Fields->register_field_type('Tainacan\Field_Types\Radio');
$Tainacan_Fields->register_field_type('Tainacan\Field_Types\Category');
global $Tainacan_Filters;
$Tainacan_Filters = new \Tainacan\Repositories\Filters();
//register filter type
$Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Range');
$Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Custom_Interval');
$Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Selectbox');
global $Tainacan_Taxonomies;

View File

@ -186,7 +186,7 @@ class DevInterface {
<?php Helpers\HtmlHelpers::collections_dropdown( $value ); ?>
<?php elseif ($prop == 'collections_ids'): ?>
<?php Helpers\HtmlHelpers::collections_checkbox_list( $value ); ?>
<?php elseif ($prop == 'field'): ?>
<?php elseif ( $prop == 'field' && $entity->get_collection_id() ): ?>
<?php Helpers\HtmlHelpers::metadata_dropdown(
$entity->get_collection_id(),
( isset( $value ) ) ? $value : '',

View File

@ -34,21 +34,6 @@ class CSV extends Importer {
return $file->fgetcsv( $this->get_delimiter() );
}
/**
* @inheritdoc
*/
public function process( $start, $end ){
while ( $start < $end && count( $this->get_processed_items() ) <= $this->get_total_items() ){
$processed_item = $this->process_item( $start );
if( $processed_item) {
$this->insert( $start, $processed_item );
} else {
$this->set_log('error', 'failed on item '.$start );
break;
}
$start++;
}
}
/**
* @inheritdoc

View File

@ -124,7 +124,7 @@ abstract class Importer {
* @param $type
* @param $message
*/
public function set_log( $type, $message ){
public function add_log($type, $message ){
$this->logs[] = [ 'type' => $type, 'message' => $message ];
}
@ -178,7 +178,9 @@ abstract class Importer {
*
* @param $index
* @return array with field_source's as the index and values for the
* item Ex: [ 'Field1' => 'value1', 'Field2' => [ 'value2','value3' ]
* item
*
* Ex: [ 'Field1' => 'value1', 'Field2' => [ 'value2','value3' ]
*/
abstract public function process_item( $index );
@ -200,7 +202,18 @@ abstract class Importer {
* @param $start init index
* @param $end last index
*/
abstract public function process( $start, $end );
public function process( $start, $end ){
while ( $start < $end && count( $this->get_processed_items() ) <= $this->get_total_items() ){
$processed_item = $this->process_item( $start );
if( $processed_item) {
$this->insert( $start, $processed_item );
} else {
$this->add_log('error', 'failed on item '.$start );
break;
}
$start++;
}
}
/**
* insert processed item from source to Tainacan
@ -212,11 +225,14 @@ abstract class Importer {
*/
public function insert( $index, $processed_item ){
global $Tainacan_Items, $Tainacan_Item_Metadata, $Tainacan_Fields;
$item = new Tainacan\Entities\Item();
$isUpdate = ( is_array( $this->processed_items ) && isset( $this->processed_items[ $index ] ) )
? $this->processed_items[ $index ] : 0;
$item = new Tainacan\Entities\Item( $isUpdate );
$itemMetadataArray = [];
if( !isset( $this->mapping ) ){
$this->set_log('error','Mapping is not set');
$this->add_log('error','Mapping is not set');
return false;
}
@ -235,20 +251,32 @@ abstract class Importer {
}
if( !empty( $itemMetadataArray ) && $this->collection instanceof Tainacan\Entities\Collection ){
$item->set_title( time() );
$item->set_collection( $this->collection );
$insertedItem = $Tainacan_Items->insert( $item );
if( $item->validate() ){
$insertedItem = $Tainacan_Items->insert( $item );
} else {
$this->add_log( 'error', 'Item ' . $index . ': '. $item->get_errors() );
return false;
}
foreach ( $itemMetadataArray as $itemMetadata ) {
$itemMetadata->set_item( $insertedItem );
$result = $Tainacan_Item_Metadata->insert( $itemMetadata );
if( $itemMetadata->validate() ){
$result = $Tainacan_Item_Metadata->insert( $itemMetadata );
} else {
$this->add_log( 'error', 'Item ' . $index . ' on field '. $itemMetadata->get_field()->get_name()
.' has error ' . $itemMetadata->get_errors() );
continue;
}
if( $result ){
$values = ( is_array( $itemMetadata->get_value() ) ) ? implode( PHP_EOL, $itemMetadata->get_value() ) : $itemMetadata->get_value();
$this->set_log( 'success', 'Item ' . $index .
$this->add_log( 'success', 'Item ' . $index .
' has inserted the values: ' . $values . ' on field: ' . $itemMetadata->get_field()->get_name() );
} else {
$this->set_log( 'error', 'Item ' . $index . ' has an error' );
$this->add_log( 'error', 'Item ' . $index . ' has an error' );
}
}
@ -263,7 +291,7 @@ abstract class Importer {
$Tainacan_Items->update( $item );
return $item;
} else {
$this->set_log( 'error', 'Collection not set');
$this->add_log( 'error', 'Collection not set');
return false;
}

View File

@ -20,7 +20,7 @@ import Numeric from '../classes/field-types/numeric/Numeric.vue';
import Date from '../classes/field-types/date/Date.vue';
import Relationship from '../classes/field-types/relationship/Relationship.vue';
import FilterRange from '../classes/filter-types/range/Range.vue';
import FilterCustomInterval from '../classes/filter-types/custom-interval/CustomInterval.vue';
import FilterSelectbox from '../classes/filter-types/selectbox/Selectbox.vue';
Vue.customElement('tainacan-text', Text);
@ -51,8 +51,8 @@ eventBus.listener();
/* Filters */
Vue.customElement('tainacan-filter-range', FilterRange);
eventFilterBus.registerComponent( 'tainacan-filter-range' );
Vue.customElement('tainacan-filter-custom-interval', FilterCustomInterval);
eventFilterBus.registerComponent( 'tainacan-filter-custom-interval' );
Vue.customElement('tainacan-filter-selectbox', FilterSelectbox);
eventFilterBus.registerComponent( 'tainacan-filter-selectbox' );

View File

@ -32,10 +32,10 @@ class TAINACAN_REST_Terms_Controller extends TAINACAN_UnitApiTestCase {
$request_body = json_encode(
array(
'filter_type' => 'range',
'filter_type' => 'custom_interval',
'filter' => [
'name' => 'Filter name',
'description' => 'This is RANGE!',
'description' => 'This is CUSTOM INTERVAL!',
]
)
);
@ -48,7 +48,7 @@ class TAINACAN_REST_Terms_Controller extends TAINACAN_UnitApiTestCase {
$data = $response->get_data();
$this->assertTrue(is_array($data) && array_key_exists('filter_type', $data), sprintf('cannot create a range, response: %s', print_r($data, true)));
$this->assertEquals('Tainacan\Filter_Types\Range', $data['filter_type']);
$this->assertEquals('Tainacan\Filter_Types\Custom_Interval', $data['filter_type']);
$this->assertEquals('Filter name', $data['name']);
}
@ -70,7 +70,7 @@ class TAINACAN_REST_Terms_Controller extends TAINACAN_UnitApiTestCase {
)
);
$filter_type = $this->tainacan_filter_factory->create_filter('range');
$filter_type = $this->tainacan_filter_factory->create_filter('custom_interval');
$filter = $this->tainacan_entity_factory->create_entity(
'filter',
@ -141,7 +141,7 @@ class TAINACAN_REST_Terms_Controller extends TAINACAN_UnitApiTestCase {
)
);
$filter_type = $this->tainacan_filter_factory->create_filter('range');
$filter_type = $this->tainacan_filter_factory->create_filter('custom_interval');
$filter = $this->tainacan_entity_factory->create_entity(
'filter',
@ -201,7 +201,7 @@ class TAINACAN_REST_Terms_Controller extends TAINACAN_UnitApiTestCase {
)
);
$filter_type = $this->tainacan_filter_factory->create_filter('range');
$filter_type = $this->tainacan_filter_factory->create_filter('custom_interval');
$filter = $this->tainacan_entity_factory->create_entity(
'filter',

View File

@ -0,0 +1,129 @@
<?php
namespace Tainacan\Tests;
/**
* Class TestCollections
*
* @package Test_Tainacan
*/
use Tainacan\Entities;
/**
* Sample test case.
*/
class CategoryFieldTypes extends TAINACAN_UnitTestCase {
function test_category_field_types() {
global $Tainacan_Item_Metadata, $Tainacan_Items, $Tainacan_Fields, $Tainacan_Terms;
$collection = $this->tainacan_entity_factory->create_entity(
'collection',
array(
'name' => 'test',
),
true
);
$tax = $this->tainacan_entity_factory->create_entity(
'taxonomy',
array(
'name' => 'tax_test',
'collections' => [$collection],
),
true
);
$field = $this->tainacan_entity_factory->create_entity(
'field',
array(
'name' => 'meta',
'description' => 'description',
'collection' => $collection,
'field_type' => 'Tainacan\Field_Types\Category',
'status' => 'publish',
'field_type_options' => [
'taxonomy_id' => $tax->get_db_identifier(),
'allow_new_terms' => false
]
),
true
);
$field2 = $this->tainacan_entity_factory->create_entity(
'field',
array(
'name' => 'meta2',
'description' => 'description',
'collection' => $collection,
'field_type' => 'Tainacan\Field_Types\Category',
'status' => 'draft',
),
true
);
$i = $this->tainacan_entity_factory->create_entity(
'item',
array(
'title' => 'item test',
'description' => 'adasdasdsa',
'collection' => $collection,
'status' => 'publish',
),
true
);
/** TODO test existing term
$term = new \Tainacan\Entities\Term();
$term->set_name('Red');
$term->validate();
$term = $Tainacan_Terms->insert($term);
$item_metadata = new \Tainacan\Entities\Item_Metadata_Entity($i, $field);
$item_metadata->set_value('Red');
$this->assertTrue($item_metadata->validate(), 'item metadata should validate because it is an existing term');
$Tainacan_Item_Metadata->insert($item_metadata);
**/
$item_metadata = new \Tainacan\Entities\Item_Metadata_Entity($i, $field);
$item_metadata->set_value('love');
$this->assertFalse($item_metadata->validate(), 'item metadata should not validate because it does not allow new terms');
// Lets change it
$options = $field->get_field_type_options();
$options['allow_new_terms'] = true;
$field->set_field_type_options($options);
$field->validate();
$field = $Tainacan_Fields->insert($field);
$item_metadata->set_field($field);
$this->assertTrue($item_metadata->validate(), 'item metada should validate because it now allows new terms');
$Tainacan_Item_Metadata->insert($item_metadata);
$checkItem = $Tainacan_Items->fetch($i->get_id());
$check_item_metadata = new \Tainacan\Entities\Item_Metadata_Entity($checkItem, $field);
$this->assertEquals('WP_Term', get_class($check_item_metadata->get_value()));
// test 2 fields with same category
$field2->set_field_type_options([
'taxonomy_id' => $tax->get_db_identifier(),
]);
$field2->set_status('publish');
$this->assertFalse($field2->validate(), 'Category Field should not validate when using a category in use by another field in the same collection');
$errors = $field2->get_errors();
$this->assertInternalType('array', $errors);
$this->assertArrayHasKey('taxonomy_id', $errors[0]);
}
}

View File

@ -194,7 +194,7 @@ class Fields extends TAINACAN_UnitTestCase {
*/
function test_metadata_field_type(){
global $Tainacan_Fields;
$this->assertEquals( 7, sizeof( $Tainacan_Fields->fetch_field_types() ) );
$this->assertEquals( 8, sizeof( $Tainacan_Fields->fetch_field_types() ) );
}

View File

@ -79,7 +79,7 @@ class Filters extends TAINACAN_UnitTestCase {
true
);
$filter_range_type = $this->tainacan_filter_factory->create_filter('range');
$filter_range_type = $this->tainacan_filter_factory->create_filter('custom_interval');
//nao devera permitir um filtro Range para o tipo string
$this->assertTrue( $filter->set_filter_type( $filter_range_type ) === null );