compostr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/README.md +94 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/compostr.gemspec +32 -0
- data/lib/compostr.rb +60 -0
- data/lib/compostr/custom_field_value.rb +36 -0
- data/lib/compostr/custom_post_type.rb +386 -0
- data/lib/compostr/entity_cache.rb +100 -0
- data/lib/compostr/image_upload.rb +31 -0
- data/lib/compostr/image_uploader.rb +37 -0
- data/lib/compostr/logging.rb +25 -0
- data/lib/compostr/media_library_cache.rb +51 -0
- data/lib/compostr/syncer.rb +88 -0
- data/lib/compostr/version.rb +3 -0
- data/lib/compostr/wp_string.rb +14 -0
- data/vcr_cassettes/syncer_push_dungeonlord.yml +62 -0
- data/vcr_cassettes/wp_failing_post_deletion.yml +61 -0
- data/vcr_cassettes/wp_successful_post_deletion.yml +62 -0
- metadata +166 -0
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Compostr is heavily WIP!
|
2
|
+
|
3
|
+
# Compostr
|
4
|
+
|
5
|
+
Compostr is extracted code from the [`wp_event` gem](https://github.com/ecovillage/wp_event), a solution to feed a specific wordpress instance with specific Custom Post Type instances.
|
6
|
+
|
7
|
+
Its a heavy WIP.
|
8
|
+
|
9
|
+
Compostr is a somewhat weirdly engineered wrapper to decorate ruby classes such that they can be pushed to (or fetched from) a wordpress installation that defines corresponing CPTs (Custom Post Types).
|
10
|
+
|
11
|
+
It would be fun to discuss on `Compostr`s development history and design decisions, but unfortunately that is out of scope for the time being.
|
12
|
+
|
13
|
+
Licensed under the GPLv3+, Copyright 2016, 2017 Felix Wolfsteller.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'compostr'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install compostr
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### In a nutshell
|
34
|
+
|
35
|
+
Define a CPT class like this (you still need Wordpress PHP code!):
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class ProgrammingLanguage < Compostr::CustomPostType
|
39
|
+
wp_post_type 'programming_language' # `post_type` as known by WP
|
40
|
+
wp_custom_field_single 'awesomeness' # 'meta' field in WP, just one value is queried and set
|
41
|
+
wp_custom_field_multi 'further_links' # 'meta' field(s) in WP, can have multiple values
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Now `ProgrammingLanguage`s can be queried and posted to your Wordpress installation. Instances of this class will automatically respond to `content`, `id`, `title` and `featured_image_id` (corresponding to the Wordpress `post_content`, `id`, `post_title` and `featured_image_id`).
|
46
|
+
|
47
|
+
Compostr comes prepared with `UUID` information of CPT instances, to e.g. distinctlive identify entities across different WP instances where entities might have different `post_id`s..
|
48
|
+
|
49
|
+
### Configuration
|
50
|
+
|
51
|
+
Global configuration is given in `compostr.conf`, where connection information to the Wordpress installation is defined:
|
52
|
+
|
53
|
+
# compostr.conf
|
54
|
+
host: "wordpress.mydomain"
|
55
|
+
username: "admin"
|
56
|
+
password: "buzzword"
|
57
|
+
language_term: "Deutsch"
|
58
|
+
author_id: 1
|
59
|
+
|
60
|
+
### Logging/Logger
|
61
|
+
|
62
|
+
Although logging should not be a main Compostr concern, it was helpful to include some handy helpers.
|
63
|
+
|
64
|
+
Use Compostr::logger if you want to feed Compostrs logs into your main applications log or redirect them somewhere.
|
65
|
+
|
66
|
+
To mixin `info`, `warn` and other logging functions into your class/module do an `include Compostr::Logging`.
|
67
|
+
|
68
|
+
To make Compostr-logs use your logger, set it like this: `Compostr::logger = <mylogger`.
|
69
|
+
|
70
|
+
### EntityCache
|
71
|
+
|
72
|
+
Until you provide some Wordpress PHP code to query custom post types via their Custom (meta) Fields, to query and work with CPTs, all data will be read into memory using `Compostr::EntityCache`.
|
73
|
+
|
74
|
+
### Syncer
|
75
|
+
|
76
|
+
The `Syncer` class deals with wordpress data updates.
|
77
|
+
|
78
|
+
To avoid re-creation of Posts and the respecive Meta Fields on update actions, prior cache population is needed and employed.
|
79
|
+
|
80
|
+
### Image Upload/Featured images
|
81
|
+
|
82
|
+
Images can be uploaded to Wordpress using the `ImageUploader` class, which comes with a Cache to avoid duplicate upload of images (where "duplication" reduces to "same name"!).
|
83
|
+
|
84
|
+
## Development
|
85
|
+
|
86
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
87
|
+
|
88
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
89
|
+
|
90
|
+
## Contributing
|
91
|
+
|
92
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ecovillage/compostr. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
93
|
+
|
94
|
+
That said, just drop me a line.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "compostr"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/compostr.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'compostr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "compostr"
|
8
|
+
spec.version = Compostr::VERSION
|
9
|
+
spec.authors = ["Felix Wolfsteller"]
|
10
|
+
spec.email = ["felix.wolfsteller@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Ease interaction with Custom Post Types of a Wordpress installation.}
|
13
|
+
spec.description = %q{One way to ask a wordpress installation about specific custom post type instances and tell it about them.}
|
14
|
+
spec.homepage = 'https://github.com/ecovillage/compostr'
|
15
|
+
spec.licenses = ['GPL-3.0+']
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.required_ruby_version = '~> 2.3.1'
|
23
|
+
|
24
|
+
spec.add_dependency "rubypress", '~> 1.2'
|
25
|
+
spec.add_dependency "mime-types", '~> 3.1'
|
26
|
+
|
27
|
+
spec.add_development_dependency "minitest", '~> 5.0'
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "webmock"
|
31
|
+
spec.add_development_dependency "vcr"
|
32
|
+
end
|
data/lib/compostr.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require "compostr/version"
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'yaml'
|
5
|
+
require 'rubypress'
|
6
|
+
|
7
|
+
require 'compostr/logging'
|
8
|
+
|
9
|
+
require 'compostr/wp_string'
|
10
|
+
|
11
|
+
require 'compostr/custom_field_value'
|
12
|
+
require 'compostr/custom_post_type'
|
13
|
+
|
14
|
+
require 'compostr/entity_cache'
|
15
|
+
require 'compostr/media_library_cache'
|
16
|
+
|
17
|
+
require 'compostr/image_upload'
|
18
|
+
require 'compostr/image_uploader'
|
19
|
+
|
20
|
+
require 'compostr/syncer'
|
21
|
+
|
22
|
+
# Get the loggers, get the config, expose WP
|
23
|
+
module Compostr
|
24
|
+
# Load and memoize 'compostr.conf'.
|
25
|
+
def self.load_conf
|
26
|
+
@@config = OpenStruct.new YAML.load_file 'compostr.conf'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Access configuration hash.
|
30
|
+
def self.config
|
31
|
+
@@config ||= load_conf
|
32
|
+
end
|
33
|
+
|
34
|
+
# Access (and/or initialize) Rubypress client, settings initially pulled
|
35
|
+
# from the configuration.
|
36
|
+
def self.wp
|
37
|
+
@wp ||= Rubypress::Client.new(host: config.host,
|
38
|
+
username: config.username,
|
39
|
+
password: config.password)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Access the logger, initialize and memoize it on demand.
|
43
|
+
def self.logger
|
44
|
+
@@logger ||= Logger.new(STDOUT)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set the logger.
|
48
|
+
def self.logger= logger
|
49
|
+
@@logger = logger
|
50
|
+
end
|
51
|
+
|
52
|
+
# Delete a post with given wordpress post_id
|
53
|
+
def self.delete_post post_id
|
54
|
+
begin
|
55
|
+
Compostr::wp.deletePost(blog_id: 0, post_id: post_id)
|
56
|
+
rescue XMLRPC::FaultException
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Compostr
|
2
|
+
# Describe a Custom Field Value with optionally an id (corresponding to the WordPress data).
|
3
|
+
class CustomFieldValue
|
4
|
+
attr_accessor :id, :key, :value
|
5
|
+
|
6
|
+
def initialize id, key, value
|
7
|
+
@id = id
|
8
|
+
@key = key
|
9
|
+
@value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
# Convert to hash that is consumable by RubyPress/Wordpress.
|
13
|
+
# Important that neither key nor value are present for custom field
|
14
|
+
# values that should be *deleted* in wordpress instance.
|
15
|
+
def to_hash
|
16
|
+
if @id
|
17
|
+
hsh = { id: @id }
|
18
|
+
hsh[:key] = @key if @key
|
19
|
+
hsh[:value] = @value if @value
|
20
|
+
hsh
|
21
|
+
else
|
22
|
+
hsh = {}
|
23
|
+
hsh[:key] = @key if @key
|
24
|
+
hsh[:value] = @value if @value
|
25
|
+
hsh
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# CustomField NullValue Object.
|
31
|
+
class NullCustomFieldValue
|
32
|
+
def id; nil; end
|
33
|
+
def key; nil; end
|
34
|
+
def value; nil; end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,386 @@
|
|
1
|
+
module Compostr
|
2
|
+
# Base class to inherit from for Classes that map to Wordpress
|
3
|
+
# Custom Post Types.
|
4
|
+
#
|
5
|
+
# Besides the post_id, title, content and featured_image (id) that
|
6
|
+
# define a post, the CustomPostType likely will own custom field
|
7
|
+
# values. These are specified with wp_custom_field_single and wp_custom_field_multi (depending on their type).
|
8
|
+
#
|
9
|
+
# To loop over the fields, use @fields and @multi_fields.
|
10
|
+
class CustomPostType
|
11
|
+
attr_accessor :post_id, :title, :content, :featured_image_id
|
12
|
+
# TODO rename to single_fields?
|
13
|
+
attr_accessor :fields, :multi_fields
|
14
|
+
|
15
|
+
# Define accessor method to the POST_TYPE (Class#post_type and
|
16
|
+
# Instance#post_type).
|
17
|
+
def self.wp_post_type(wp_post_type)
|
18
|
+
# TODO syntax: define_method("....")
|
19
|
+
|
20
|
+
# Class wide variable (could also be a constant)
|
21
|
+
self.class_eval("POST_TYPE = '#{wp_post_type}'.freeze")
|
22
|
+
# Class accessor method
|
23
|
+
# def self.post_type
|
24
|
+
# POST_TYPE
|
25
|
+
# end
|
26
|
+
self.class_eval("def self.post_type; POST_TYPE; end")
|
27
|
+
# Instance accessor method
|
28
|
+
# def post_type
|
29
|
+
# POST_TYPE
|
30
|
+
# end
|
31
|
+
self.class_eval("def post_type; POST_TYPE; end")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Defines accessor methods for the field, which will only
|
35
|
+
# allow a single value.
|
36
|
+
#
|
37
|
+
# Note that the accessor only wears strings and automatically strips
|
38
|
+
def self.wp_custom_field_single(field_key)
|
39
|
+
# def field_key=(new_value)
|
40
|
+
# field!('field_key') = new_value.to_s.strip
|
41
|
+
# end
|
42
|
+
self.class_eval("def #{field_key.to_s}=(new_value); field!('#{field_key.to_s}').value = WPString.wp_string(new_value); end")
|
43
|
+
# def field_key
|
44
|
+
# field?(field_key).value
|
45
|
+
# end
|
46
|
+
self.class_eval("def #{field_key.to_s}; return field?('#{field_key.to_s}').value; end")
|
47
|
+
|
48
|
+
# Add field to @supported_(single_)fields.
|
49
|
+
# This is declared in the class, thus a kindof CLASS variable!
|
50
|
+
self.class_eval("(@supported_single_fields ||= []) << '#{field_key}'")
|
51
|
+
end
|
52
|
+
|
53
|
+
# Specify a field that will make and take a fine array.
|
54
|
+
def self.wp_custom_field_multi(field_key)
|
55
|
+
# def field_key=(new_value)
|
56
|
+
# multi_field('field_key') = new_value.map{|v| CustomFieldValue.new(nil, 'field_key', v)}
|
57
|
+
# end
|
58
|
+
# TODO recycle!
|
59
|
+
self.class_eval("def #{field_key.to_s}=(new_value); @multi_fields['#{field_key.to_s}'] = new_value.map{|v| CustomFieldValue.new(nil, '#{field_key.to_s}', v)}; end")
|
60
|
+
# def field_key
|
61
|
+
# multi_field(field_key).map(&:value).compact
|
62
|
+
# end
|
63
|
+
self.class_eval("def #{field_key.to_s}; return multi_field('#{field_key.to_s}').map(&:value).compact; end")
|
64
|
+
|
65
|
+
# Add field to @supported_(multi_)fields.
|
66
|
+
# This is declared in the class, thus a kindof CLASS variable!
|
67
|
+
self.class_eval("(@supported_multi_fields ||= []) << '#{field_key}'")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Alias the post_title getter and setter with another 'name'.
|
71
|
+
def self.wp_post_title_alias(title_alias)
|
72
|
+
self.class_eval("alias :#{title_alias.to_sym}= :title=")
|
73
|
+
self.class_eval("alias :#{title_alias.to_sym} :title")
|
74
|
+
end
|
75
|
+
|
76
|
+
# Alias the post_content getter and setter with another 'name'.
|
77
|
+
def self.wp_post_content_alias(content_alias)
|
78
|
+
self.class_eval("alias :#{content_alias.to_sym}= :content=")
|
79
|
+
self.class_eval("alias :#{content_alias.to_sym} :content")
|
80
|
+
end
|
81
|
+
|
82
|
+
# This is an instance variable for the Class (not for instances of it)!
|
83
|
+
# Three values are allowed: :ignore, :delete, :add
|
84
|
+
# And this should actually be two variables: one for
|
85
|
+
# from_content_hash and one for integrate_ids
|
86
|
+
@additional_field_action = :ignore
|
87
|
+
|
88
|
+
# Define whether additional custom fields should be
|
89
|
+
# :ignore -> ignored (default)
|
90
|
+
# :delete -> marked for deletion
|
91
|
+
# :add -> added
|
92
|
+
# Other values for action will silently be ignored.
|
93
|
+
def self.additional_field_action(action)
|
94
|
+
if [:ignore, :delete, :add].include? action.to_sym
|
95
|
+
# @additional_field_action = :action
|
96
|
+
self.class_eval("@additional_field_action = :#{action}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def additional_field_action
|
101
|
+
self.class.instance_variable_get(:@additional_field_action) || :ignore
|
102
|
+
end
|
103
|
+
|
104
|
+
def initialize **kwargs
|
105
|
+
@fields = Hash.new
|
106
|
+
# This one is painful, maybe field? and field!?
|
107
|
+
#@fields.default_proc = proc do |hash, key|
|
108
|
+
# hash[key] = CustomFieldValue.new(nil, key, nil)
|
109
|
+
#end
|
110
|
+
@multi_fields = Hash.new
|
111
|
+
@multi_fields.default_proc = proc do |hash, key|
|
112
|
+
hash[key] = []
|
113
|
+
end
|
114
|
+
kwargs.each do |k,v|
|
115
|
+
if k == :title
|
116
|
+
# strip ?
|
117
|
+
@title = v
|
118
|
+
elsif k == :content
|
119
|
+
@content = v
|
120
|
+
elsif k == :post_id
|
121
|
+
@post_id = v
|
122
|
+
elsif k == :featured_image_id
|
123
|
+
@featured_image_id = v
|
124
|
+
# Better: has_custom_field?
|
125
|
+
elsif respond_to?(k.to_sym)
|
126
|
+
self.send(((k.to_s) + "=").to_sym, v)
|
127
|
+
elsif additional_field_action == :add
|
128
|
+
@fields[k.to_sym] = CustomFieldValue.new(nil, k.to_sym, v)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def custom_fields_hash
|
134
|
+
@fields.values.map(&:to_hash)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Access the given field, returns a NullCustomFieldValue if not found.
|
138
|
+
# The NullCustomFieldValue does not accept setting any values and
|
139
|
+
# returns nil for id, key and value.
|
140
|
+
#
|
141
|
+
# Use this to (readonly) access a field with given name.
|
142
|
+
def field?(field_name)
|
143
|
+
if @fields.key? field_name
|
144
|
+
@fields[field_name]
|
145
|
+
else
|
146
|
+
NullCustomFieldValue.new
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Access (or create) a CustomFieldValue that can hold a single value.
|
151
|
+
def field!(field_name)
|
152
|
+
# ||= would probably do, too.
|
153
|
+
if @fields.key? field_name
|
154
|
+
@fields[field_name]
|
155
|
+
else
|
156
|
+
@fields[field_name] = CustomFieldValue.new(nil, field_name, nil)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Access a CustomFieldValue that can hold multiple values (array).
|
161
|
+
def multi_field(field_name)
|
162
|
+
@multi_fields[field_name]
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns list of field keys generally supported by this Custom Post Type.
|
166
|
+
def supported_fields
|
167
|
+
self.class.supported_fields
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns list of field keys generally supported by this Custom Post Type.
|
171
|
+
def self.supported_fields
|
172
|
+
supported_single_fields | supported_multi_fields
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns list of single-valued field keys generally supported
|
176
|
+
# by this Custom Post Type.
|
177
|
+
def self.supported_single_fields
|
178
|
+
instance_variable_get(:@supported_single_fields) || []
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns list of multiple-valued field keys generally supported
|
182
|
+
# by this Custom Post Type.
|
183
|
+
def self.supported_multi_fields
|
184
|
+
instance_variable_get(:@supported_multi_fields) || []
|
185
|
+
end
|
186
|
+
|
187
|
+
# True iff supported fields include field_name
|
188
|
+
def has_custom_field? field_name
|
189
|
+
supported_fields.include? field_name
|
190
|
+
end
|
191
|
+
|
192
|
+
# From a Hash as returned by RubyPress's getPost(s) method
|
193
|
+
# populate and return a new CustomPostType-instance.
|
194
|
+
#
|
195
|
+
# Custom field values will be created as specified by
|
196
|
+
# the wp_custom_field_single/multi definitions.
|
197
|
+
def self.from_content_hash content_hash
|
198
|
+
return nil if content_hash.nil?
|
199
|
+
entity = new(post_id: content_hash["post_id"],
|
200
|
+
content: content_hash["post_content"],
|
201
|
+
title: content_hash["post_title"])
|
202
|
+
|
203
|
+
custom_fields_list = content_hash["custom_fields"] || []
|
204
|
+
|
205
|
+
supported_fields.each do |field_key|
|
206
|
+
#puts "iterating over supported field #{field_key}"
|
207
|
+
if is_single_field? field_key
|
208
|
+
# Here: duplicate deletion possible
|
209
|
+
field = custom_fields_list.find{|f| f["key"] == field_key}
|
210
|
+
if field
|
211
|
+
entity.send("#{field_key}=".to_sym, field["value"])
|
212
|
+
entity.field?(field_key).id = field["id"]
|
213
|
+
end
|
214
|
+
else
|
215
|
+
fields = custom_fields_list.select{|f| f["key"] == field_key}
|
216
|
+
values = fields.map{|f| f["value"]}
|
217
|
+
entity.send("#{field_key}=".to_sym, values)
|
218
|
+
# Not elegant: Set the id one per one
|
219
|
+
fields.each do |f|
|
220
|
+
entity.set_field_id(field_key, f["value"], f["id"])
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
# if additional fields add, add these
|
225
|
+
|
226
|
+
entity
|
227
|
+
end
|
228
|
+
|
229
|
+
def to_content_hash
|
230
|
+
content = {
|
231
|
+
post_type: post_type,
|
232
|
+
post_status: 'publish',
|
233
|
+
post_data: Time.now,
|
234
|
+
post_title: title || '', # why does content need '@'?
|
235
|
+
post_content: @content || '',
|
236
|
+
custom_fields: @fields.map{|k,v| v.to_hash} | @multi_fields.flat_map{|k,v| v.flat_map(&:to_hash)}
|
237
|
+
}
|
238
|
+
if featured_image_id
|
239
|
+
content[:post_thumbnail] = featured_image_id.to_s
|
240
|
+
end
|
241
|
+
content
|
242
|
+
end
|
243
|
+
|
244
|
+
# When additional_field_action == :ignore (the default) sets (wp) ids
|
245
|
+
# of fields for which values are set.
|
246
|
+
#
|
247
|
+
# If additional_field_action == :add CustomFieldValues of other_entity are
|
248
|
+
# copied if not yet existing in this entity (otherwise only the id of
|
249
|
+
# the fields are set.
|
250
|
+
#
|
251
|
+
# Finally, if additional_field_action == :delete , mark the fields which
|
252
|
+
# are NOT set in this entity but in the other entity ready for deletion.
|
253
|
+
#
|
254
|
+
# The ids are taken from other_entity (if available, left empty otherwise).
|
255
|
+
def integrate_field_ids other_entity
|
256
|
+
# TODO rename and/or restructure this method
|
257
|
+
# new from old
|
258
|
+
fields.values.each do |f|
|
259
|
+
if f.key.start_with? 'ref'
|
260
|
+
puts "foreign fields : #{other_entity.fields.keys}"
|
261
|
+
puts "foreign fields : #{other_entity.fields.values.map{|v| v.id.to_s + ' ' + v.id.class.to_s}}"
|
262
|
+
puts "integrate field : #{f.key} #{f.inspect}"
|
263
|
+
puts " other field : #{other_entity.field?(f.key).inspect}"
|
264
|
+
end
|
265
|
+
f.id = other_entity.field?(f.key).id
|
266
|
+
end
|
267
|
+
|
268
|
+
if additional_field_action == :add
|
269
|
+
# old to new
|
270
|
+
other_entity.fields.values.each do |f|
|
271
|
+
if !@fields.key?(f.key)
|
272
|
+
@fields[f.key] = f
|
273
|
+
end
|
274
|
+
end
|
275
|
+
elsif additional_field_action == :delete
|
276
|
+
other_entity.fields.values.each do |f|
|
277
|
+
if !@fields.key?(f.key)
|
278
|
+
# This field will be deleted when used to edit Post
|
279
|
+
@fields[f.key] = CustomFieldValue.new(f.id, nil, nil)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
@multi_fields.each do |field_name, mf|
|
285
|
+
ids = other_entity.multi_field(field_name).map(&:id)
|
286
|
+
mf.each do |mf_entry|
|
287
|
+
mf_entry.id = ids.delete_at(0) # keep order, use #pop otherwise
|
288
|
+
end
|
289
|
+
# If any ids left, delete these custom fields
|
290
|
+
ids.each do |id|
|
291
|
+
multi_field(field_name) << CustomFieldValue.new(id, nil, nil)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def is_multi_field?(field_name)
|
297
|
+
self.class.is_multi_field?(field_name)
|
298
|
+
end
|
299
|
+
|
300
|
+
def self.is_multi_field?(field_name)
|
301
|
+
supported_multi_fields.include? field_name
|
302
|
+
end
|
303
|
+
|
304
|
+
def is_single_field?(field_name)
|
305
|
+
self.class.is_single_field?(field_name)
|
306
|
+
end
|
307
|
+
|
308
|
+
def self.is_single_field?(field_name)
|
309
|
+
supported_single_fields.include? field_name
|
310
|
+
end
|
311
|
+
|
312
|
+
def in_wordpress?
|
313
|
+
post_id.to_s != '' && !!post_id
|
314
|
+
end
|
315
|
+
|
316
|
+
def set_field_id field_key, field_value, field_id
|
317
|
+
if is_single_field? field_key
|
318
|
+
# ????!! field!
|
319
|
+
field?(field_key).id = field_id
|
320
|
+
else
|
321
|
+
multi_field(field_key).find{|f| f.key == field_key && f.value == field_value}.id = field_id
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Returns hash where keys are field names where the values differ.
|
326
|
+
# values of returned hash are arrays like
|
327
|
+
# [own_value, other_different_value].
|
328
|
+
# Returns empty hash to signalize equaliness.
|
329
|
+
def diff(other_cpt_object)
|
330
|
+
if other_cpt_object.nil?
|
331
|
+
other_cpt_object = NullCustomPostType.new
|
332
|
+
end
|
333
|
+
|
334
|
+
diff_fields = {}
|
335
|
+
# Fields exclusive to this one.
|
336
|
+
(@fields.keys - other_cpt_object.fields.keys).each do |f|
|
337
|
+
diff_fields[f] = [@fields[f].value, nil]
|
338
|
+
end
|
339
|
+
# Fields exclusive to the other.
|
340
|
+
(other_cpt_object.fields.keys - @fields.keys).each do |f|
|
341
|
+
diff_fields[f] = [nil, other_cpt_object.fields[f].value]
|
342
|
+
end
|
343
|
+
# Mutual fields
|
344
|
+
(@fields.keys | other_cpt_object.fields.keys).each do |f|
|
345
|
+
field_value = field?(f).value
|
346
|
+
other_field_value = other_cpt_object.field?(f).value
|
347
|
+
if other_field_value != field_value
|
348
|
+
diff_fields[f] = [field_value, other_field_value]
|
349
|
+
end
|
350
|
+
end
|
351
|
+
# Multi-Fields exclusive to this one.
|
352
|
+
(@multi_fields.keys - other_cpt_object.multi_fields.keys).each do |f|
|
353
|
+
diff_fields[f] = [@multi_fields[f].map(&:value), nil]
|
354
|
+
end
|
355
|
+
# Multi-Fields exclusive to the other.
|
356
|
+
(other_cpt_object.multi_fields.keys - @multi_fields.keys).each do |f|
|
357
|
+
diff_fields[f] = [nil, other_cpt_object.multi_fields[f].value]
|
358
|
+
end
|
359
|
+
# Mutual Multi-fields
|
360
|
+
(@multi_fields.keys | other_cpt_object.multi_fields.keys).each do |f|
|
361
|
+
field_values = multi_field(f).map(&:value).compact
|
362
|
+
other_field_values = other_cpt_object.multi_field(f).map(&:value).compact
|
363
|
+
if other_field_values != field_values
|
364
|
+
diff_fields[f] = [field_values, other_field_values]
|
365
|
+
end
|
366
|
+
end
|
367
|
+
if @title.to_s.strip != other_cpt_object.title.to_s.strip
|
368
|
+
diff_fields["title"] = [@title, other_cpt_object.title]
|
369
|
+
end
|
370
|
+
if @featured_image_id != other_cpt_object.featured_image_id
|
371
|
+
diff_fields["featured_image_id"] = [@featured_image_id, other_cpt_object.featured_image_id]
|
372
|
+
end
|
373
|
+
if @content.to_s.strip != other_cpt_object.content.to_s.strip
|
374
|
+
diff_fields["content"] = [@content, other_cpt_object.content]
|
375
|
+
end
|
376
|
+
diff_fields
|
377
|
+
end
|
378
|
+
|
379
|
+
def different_from? other_cpt_object
|
380
|
+
!diff(other_cpt_object).empty?
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
class NullCustomPostType < CustomPostType
|
385
|
+
end
|
386
|
+
end
|