Facebook for WooCommerce - Version 1.9.11

Version Description

  • 2019-02-26 =
  • changing contributor to facebook from facebook4woocommerce, so that woo plugin will be shown under https://profiles.wordpress.org/facebook/#content-plugins
  • adding changelog in readme.txt so that notifications will be sent for updates and changelog will be shown under https://wordpress.org/plugins/facebook-for-woocommerce/#developers
  • removing debug flags notice under facebook-for-woocommerce.php so that developers will be able to debug with debug logs
Download this release

Release Info

Developer facebook4woocommerce
Plugin Icon Facebook for WooCommerce
Version 1.9.11
Comparing to
See all releases

Version 1.9.11

CODE_OF_CONDUCT.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ # Code of Conduct
2
+
3
+ Facebook has adopted a Code of Conduct that we expect project participants to adhere to.
4
+ Please read the [full text](https://code.fb.com/codeofconduct/)
5
+ so that you can understand what actions will and will not be tolerated.
CONTRIBUTING.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to Facebook for WooCommerce
2
+ We want to make contributing to this project as easy and transparent as
3
+ possible.
4
+
5
+ ## Pull Requests
6
+ We actively welcome your pull requests.
7
+
8
+ 1. Fork the repo and create your branch from `master`.
9
+ 2. If you've added code that should be tested, add tests.
10
+ 3. If you've changed APIs, update the documentation.
11
+ 4. Make sure your code lints.
12
+ 5. If you haven't already, complete the Contributor License Agreement ("CLA").
13
+
14
+ ## Contributor License Agreement ("CLA")
15
+ In order to accept your pull request, we need you to submit a CLA. You only need
16
+ to do this once to work on any of Facebook's open source projects.
17
+
18
+ Complete your CLA here: <https://code.facebook.com/cla>
19
+
20
+ ## Issues
21
+ We use GitHub issues to track public bugs. Please ensure your description is
22
+ clear and has sufficient instructions to be able to reproduce the issue.
23
+
24
+ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
25
+ disclosure of security bugs. In those cases, please go through the process
26
+ outlined on that page and do not file a public issue.
27
+
28
+ ## Coding Style
29
+ * 4 spaces for indentation rather than tabs
30
+ * 80 character line length
31
+
32
+ ## License
33
+ By contributing to Facebook for WooCommerce, you agree that your contributions
34
+ will be licensed under the LICENSE file in the root directory of
35
+ this source tree.
LICENSE ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 2, June 1991
3
+
4
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
+ Everyone is permitted to copy and distribute verbatim copies
7
+ of this license document, but changing it is not allowed.
8
+
9
+ Preamble
10
+
11
+ The licenses for most software are designed to take away your
12
+ freedom to share and change it. By contrast, the GNU General Public
13
+ License is intended to guarantee your freedom to share and change free
14
+ software--to make sure the software is free for all its users. This
15
+ General Public License applies to most of the Free Software
16
+ Foundation's software and to any other program whose authors commit to
17
+ using it. (Some other Free Software Foundation software is covered by
18
+ the GNU Lesser General Public License instead.) You can apply it to
19
+ your programs, too.
20
+
21
+ When we speak of free software, we are referring to freedom, not
22
+ price. Our General Public Licenses are designed to make sure that you
23
+ have the freedom to distribute copies of free software (and charge for
24
+ this service if you wish), that you receive source code or can get it
25
+ if you want it, that you can change the software or use pieces of it
26
+ in new free programs; and that you know you can do these things.
27
+
28
+ To protect your rights, we need to make restrictions that forbid
29
+ anyone to deny you these rights or to ask you to surrender the rights.
30
+ These restrictions translate to certain responsibilities for you if you
31
+ distribute copies of the software, or if you modify it.
32
+
33
+ For example, if you distribute copies of such a program, whether
34
+ gratis or for a fee, you must give the recipients all the rights that
35
+ you have. You must make sure that they, too, receive or can get the
36
+ source code. And you must show them these terms so they know their
37
+ rights.
38
+
39
+ We protect your rights with two steps: (1) copyright the software, and
40
+ (2) offer you this license which gives you legal permission to copy,
41
+ distribute and/or modify the software.
42
+
43
+ Also, for each author's protection and ours, we want to make certain
44
+ that everyone understands that there is no warranty for this free
45
+ software. If the software is modified by someone else and passed on, we
46
+ want its recipients to know that what they have is not the original, so
47
+ that any problems introduced by others will not reflect on the original
48
+ authors' reputations.
49
+
50
+ Finally, any free program is threatened constantly by software
51
+ patents. We wish to avoid the danger that redistributors of a free
52
+ program will individually obtain patent licenses, in effect making the
53
+ program proprietary. To prevent this, we have made it clear that any
54
+ patent must be licensed for everyone's free use or not licensed at all.
55
+
56
+ The precise terms and conditions for copying, distribution and
57
+ modification follow.
58
+
59
+ GNU GENERAL PUBLIC LICENSE
60
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
+
62
+ 0. This License applies to any program or other work which contains
63
+ a notice placed by the copyright holder saying it may be distributed
64
+ under the terms of this General Public License. The "Program", below,
65
+ refers to any such program or work, and a "work based on the Program"
66
+ means either the Program or any derivative work under copyright law:
67
+ that is to say, a work containing the Program or a portion of it,
68
+ either verbatim or with modifications and/or translated into another
69
+ language. (Hereinafter, translation is included without limitation in
70
+ the term "modification".) Each licensee is addressed as "you".
71
+
72
+ Activities other than copying, distribution and modification are not
73
+ covered by this License; they are outside its scope. The act of
74
+ running the Program is not restricted, and the output from the Program
75
+ is covered only if its contents constitute a work based on the
76
+ Program (independent of having been made by running the Program).
77
+ Whether that is true depends on what the Program does.
78
+
79
+ 1. You may copy and distribute verbatim copies of the Program's
80
+ source code as you receive it, in any medium, provided that you
81
+ conspicuously and appropriately publish on each copy an appropriate
82
+ copyright notice and disclaimer of warranty; keep intact all the
83
+ notices that refer to this License and to the absence of any warranty;
84
+ and give any other recipients of the Program a copy of this License
85
+ along with the Program.
86
+
87
+ You may charge a fee for the physical act of transferring a copy, and
88
+ you may at your option offer warranty protection in exchange for a fee.
89
+
90
+ 2. You may modify your copy or copies of the Program or any portion
91
+ of it, thus forming a work based on the Program, and copy and
92
+ distribute such modifications or work under the terms of Section 1
93
+ above, provided that you also meet all of these conditions:
94
+
95
+ a) You must cause the modified files to carry prominent notices
96
+ stating that you changed the files and the date of any change.
97
+
98
+ b) You must cause any work that you distribute or publish, that in
99
+ whole or in part contains or is derived from the Program or any
100
+ part thereof, to be licensed as a whole at no charge to all third
101
+ parties under the terms of this License.
102
+
103
+ c) If the modified program normally reads commands interactively
104
+ when run, you must cause it, when started running for such
105
+ interactive use in the most ordinary way, to print or display an
106
+ announcement including an appropriate copyright notice and a
107
+ notice that there is no warranty (or else, saying that you provide
108
+ a warranty) and that users may redistribute the program under
109
+ these conditions, and telling the user how to view a copy of this
110
+ License. (Exception: if the Program itself is interactive but
111
+ does not normally print such an announcement, your work based on
112
+ the Program is not required to print an announcement.)
113
+
114
+ These requirements apply to the modified work as a whole. If
115
+ identifiable sections of that work are not derived from the Program,
116
+ and can be reasonably considered independent and separate works in
117
+ themselves, then this License, and its terms, do not apply to those
118
+ sections when you distribute them as separate works. But when you
119
+ distribute the same sections as part of a whole which is a work based
120
+ on the Program, the distribution of the whole must be on the terms of
121
+ this License, whose permissions for other licensees extend to the
122
+ entire whole, and thus to each and every part regardless of who wrote it.
123
+
124
+ Thus, it is not the intent of this section to claim rights or contest
125
+ your rights to work written entirely by you; rather, the intent is to
126
+ exercise the right to control the distribution of derivative or
127
+ collective works based on the Program.
128
+
129
+ In addition, mere aggregation of another work not based on the Program
130
+ with the Program (or with a work based on the Program) on a volume of
131
+ a storage or distribution medium does not bring the other work under
132
+ the scope of this License.
133
+
134
+ 3. You may copy and distribute the Program (or a work based on it,
135
+ under Section 2) in object code or executable form under the terms of
136
+ Sections 1 and 2 above provided that you also do one of the following:
137
+
138
+ a) Accompany it with the complete corresponding machine-readable
139
+ source code, which must be distributed under the terms of Sections
140
+ 1 and 2 above on a medium customarily used for software interchange; or,
141
+
142
+ b) Accompany it with a written offer, valid for at least three
143
+ years, to give any third party, for a charge no more than your
144
+ cost of physically performing source distribution, a complete
145
+ machine-readable copy of the corresponding source code, to be
146
+ distributed under the terms of Sections 1 and 2 above on a medium
147
+ customarily used for software interchange; or,
148
+
149
+ c) Accompany it with the information you received as to the offer
150
+ to distribute corresponding source code. (This alternative is
151
+ allowed only for noncommercial distribution and only if you
152
+ received the program in object code or executable form with such
153
+ an offer, in accord with Subsection b above.)
154
+
155
+ The source code for a work means the preferred form of the work for
156
+ making modifications to it. For an executable work, complete source
157
+ code means all the source code for all modules it contains, plus any
158
+ associated interface definition files, plus the scripts used to
159
+ control compilation and installation of the executable. However, as a
160
+ special exception, the source code distributed need not include
161
+ anything that is normally distributed (in either source or binary
162
+ form) with the major components (compiler, kernel, and so on) of the
163
+ operating system on which the executable runs, unless that component
164
+ itself accompanies the executable.
165
+
166
+ If distribution of executable or object code is made by offering
167
+ access to copy from a designated place, then offering equivalent
168
+ access to copy the source code from the same place counts as
169
+ distribution of the source code, even though third parties are not
170
+ compelled to copy the source along with the object code.
171
+
172
+ 4. You may not copy, modify, sublicense, or distribute the Program
173
+ except as expressly provided under this License. Any attempt
174
+ otherwise to copy, modify, sublicense or distribute the Program is
175
+ void, and will automatically terminate your rights under this License.
176
+ However, parties who have received copies, or rights, from you under
177
+ this License will not have their licenses terminated so long as such
178
+ parties remain in full compliance.
179
+
180
+ 5. You are not required to accept this License, since you have not
181
+ signed it. However, nothing else grants you permission to modify or
182
+ distribute the Program or its derivative works. These actions are
183
+ prohibited by law if you do not accept this License. Therefore, by
184
+ modifying or distributing the Program (or any work based on the
185
+ Program), you indicate your acceptance of this License to do so, and
186
+ all its terms and conditions for copying, distributing or modifying
187
+ the Program or works based on it.
188
+
189
+ 6. Each time you redistribute the Program (or any work based on the
190
+ Program), the recipient automatically receives a license from the
191
+ original licensor to copy, distribute or modify the Program subject to
192
+ these terms and conditions. You may not impose any further
193
+ restrictions on the recipients' exercise of the rights granted herein.
194
+ You are not responsible for enforcing compliance by third parties to
195
+ this License.
196
+
197
+ 7. If, as a consequence of a court judgment or allegation of patent
198
+ infringement or for any other reason (not limited to patent issues),
199
+ conditions are imposed on you (whether by court order, agreement or
200
+ otherwise) that contradict the conditions of this License, they do not
201
+ excuse you from the conditions of this License. If you cannot
202
+ distribute so as to satisfy simultaneously your obligations under this
203
+ License and any other pertinent obligations, then as a consequence you
204
+ may not distribute the Program at all. For example, if a patent
205
+ license would not permit royalty-free redistribution of the Program by
206
+ all those who receive copies directly or indirectly through you, then
207
+ the only way you could satisfy both it and this License would be to
208
+ refrain entirely from distribution of the Program.
209
+
210
+ If any portion of this section is held invalid or unenforceable under
211
+ any particular circumstance, the balance of the section is intended to
212
+ apply and the section as a whole is intended to apply in other
213
+ circumstances.
214
+
215
+ It is not the purpose of this section to induce you to infringe any
216
+ patents or other property right claims or to contest validity of any
217
+ such claims; this section has the sole purpose of protecting the
218
+ integrity of the free software distribution system, which is
219
+ implemented by public license practices. Many people have made
220
+ generous contributions to the wide range of software distributed
221
+ through that system in reliance on consistent application of that
222
+ system; it is up to the author/donor to decide if he or she is willing
223
+ to distribute software through any other system and a licensee cannot
224
+ impose that choice.
225
+
226
+ This section is intended to make thoroughly clear what is believed to
227
+ be a consequence of the rest of this License.
228
+
229
+ 8. If the distribution and/or use of the Program is restricted in
230
+ certain countries either by patents or by copyrighted interfaces, the
231
+ original copyright holder who places the Program under this License
232
+ may add an explicit geographical distribution limitation excluding
233
+ those countries, so that distribution is permitted only in or among
234
+ countries not thus excluded. In such case, this License incorporates
235
+ the limitation as if written in the body of this License.
236
+
237
+ 9. The Free Software Foundation may publish revised and/or new versions
238
+ of the General Public License from time to time. Such new versions will
239
+ be similar in spirit to the present version, but may differ in detail to
240
+ address new problems or concerns.
241
+
242
+ Each version is given a distinguishing version number. If the Program
243
+ specifies a version number of this License which applies to it and "any
244
+ later version", you have the option of following the terms and conditions
245
+ either of that version or of any later version published by the Free
246
+ Software Foundation. If the Program does not specify a version number of
247
+ this License, you may choose any version ever published by the Free Software
248
+ Foundation.
249
+
250
+ 10. If you wish to incorporate parts of the Program into other free
251
+ programs whose distribution conditions are different, write to the author
252
+ to ask for permission. For software which is copyrighted by the Free
253
+ Software Foundation, write to the Free Software Foundation; we sometimes
254
+ make exceptions for this. Our decision will be guided by the two goals
255
+ of preserving the free status of all derivatives of our free software and
256
+ of promoting the sharing and reuse of software generally.
257
+
258
+ NO WARRANTY
259
+
260
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
+ REPAIR OR CORRECTION.
269
+
270
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
+ POSSIBILITY OF SUCH DAMAGES.
279
+
280
+ END OF TERMS AND CONDITIONS
281
+
282
+ How to Apply These Terms to Your New Programs
283
+
284
+ If you develop a new program, and you want it to be of the greatest
285
+ possible use to the public, the best way to achieve this is to make it
286
+ free software which everyone can redistribute and change under these terms.
287
+
288
+ To do so, attach the following notices to the program. It is safest
289
+ to attach them to the start of each source file to most effectively
290
+ convey the exclusion of warranty; and each file should have at least
291
+ the "copyright" line and a pointer to where the full notice is found.
292
+
293
+ <one line to give the program's name and a brief idea of what it does.>
294
+ Copyright (C) <year> <name of author>
295
+
296
+ This program is free software; you can redistribute it and/or modify
297
+ it under the terms of the GNU General Public License as published by
298
+ the Free Software Foundation; either version 2 of the License, or
299
+ (at your option) any later version.
300
+
301
+ This program is distributed in the hope that it will be useful,
302
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
303
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304
+ GNU General Public License for more details.
305
+
306
+ You should have received a copy of the GNU General Public License along
307
+ with this program; if not, write to the Free Software Foundation, Inc.,
308
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
+
310
+ Also add information on how to contact you by electronic and paper mail.
311
+
312
+ If the program is interactive, make it output a short notice like this
313
+ when it starts in an interactive mode:
314
+
315
+ Gnomovision version 69, Copyright (C) year name of author
316
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317
+ This is free software, and you are welcome to redistribute it
318
+ under certain conditions; type `show c' for details.
319
+
320
+ The hypothetical commands `show w' and `show c' should show the appropriate
321
+ parts of the General Public License. Of course, the commands you use may
322
+ be called something other than `show w' and `show c'; they could even be
323
+ mouse-clicks or menu items--whatever suits your program.
324
+
325
+ You should also get your employer (if you work as a programmer) or your
326
+ school, if any, to sign a "copyright disclaimer" for the program, if
327
+ necessary. Here is a sample; alter the names:
328
+
329
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
+
332
+ <signature of Ty Coon>, 1 April 1989
333
+ Ty Coon, President of Vice
334
+
335
+ This General Public License does not permit incorporating your program into
336
+ proprietary programs. If your program is a subroutine library, you may
337
+ consider it more useful to permit linking proprietary applications with the
338
+ library. If this is what you want to do, use the GNU Lesser General
339
+ Public License instead of this License.
assets/css/facebook-infobanner.css ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+ #fbinfobanner .btn {
10
+ /* container: */
11
+ background-color: rgb(86,120,227);
12
+ border: 1px solid;
13
+ border-color: #435a8b #3c5488 #334c83;
14
+ border-radius: 2px;
15
+ padding: 5px 10px 5px 10px;
16
+ /* Get started: */
17
+ font-family: helvetica, arial, sans-serif;
18
+ font-size: 12px;
19
+ font-weight: 600;
20
+ color: #FFFFFF;
21
+ text-align: center;
22
+ text-decoration: none;
23
+ display: inline-block;
24
+ margin: 5px 10px 1px 1px;
25
+ }
26
+
27
+ #fbinfobanner .btn.dismiss {
28
+ margin: 5px 10px 5px 1px;
29
+ }
30
+
31
+ #fbinfobanner .btn.grey{
32
+ background: #f6f7f8;
33
+ border-color: #e0e1e2;
34
+ color: #4e5665;
35
+ }
36
+
37
+ #fbinfobanner .iconDetails {
38
+ margin: 5px 10px 5px 3px;
39
+ float:left;
40
+ height:50px;
41
+ width:50px;
42
+ }
43
+
44
+ #fbinfobanner .tipTitle {
45
+ padding: 1px 12px 1px 20px;
46
+ font-size: 14px;
47
+ }
48
+
49
+ #fbinfobanner .tipContent {
50
+ padding: 1px 12px 1px 20px;
51
+ font-size: 14px;
52
+ }
53
+
54
+ #fbinfobanner .tipButton {
55
+ margin-bottom: 0px;
56
+ }
assets/css/facebook.css ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+ #fbsetup .wrapper {
10
+ box-shadow: inset 0 0 0 1px rgba(0,0,0,0.2);
11
+ display: block;
12
+ width: 600px;
13
+ background: white url(../image/small-store.png) no-repeat;
14
+ background-position-x: 92%;
15
+ background-position-y: 172px;
16
+ background-size: 180px;
17
+ }
18
+
19
+ #fbsetup .wrapper .help-center {
20
+ float: right;
21
+ margin-top: -23px;
22
+ }
23
+
24
+ #fbsetup .wrapper .help-center a {
25
+ color: white;
26
+ }
27
+
28
+ #fbsetup .wrapper .help-center-icon {
29
+ display: inline-block;
30
+ background-image: url(../image/help-center.png);
31
+ background-size: 38px 38px;
32
+ background-repeat: no-repeat;
33
+ width: 38px;
34
+ height: 38px;
35
+ zoom: 0.7;
36
+ top: 12px;
37
+ position: relative;
38
+ }
39
+
40
+ #fbsetup .content {
41
+ padding: 10px 30px 20px 30px;
42
+ }
43
+
44
+ #fbsetup .loader {
45
+ border: 4px solid #f3f3f3; /* Light grey */
46
+ border-top: 4px solid #3498db; /* Blue */
47
+ border-radius: 50%;
48
+ width: 20px;
49
+ height: 20px;
50
+ animation: spin 2s linear infinite;
51
+ }
52
+ @keyframes spin {
53
+ 0% { transform: rotate(0deg); }
54
+ 100% { transform: rotate(360deg); }
55
+ }
56
+
57
+ #fbsetup .btn {
58
+ /* container: */
59
+ background-color: rgb(86,120,227);
60
+ border: 1px solid;
61
+ border-color: #435a8b #3c5488 #334c83;
62
+ border-radius: 2px;
63
+ padding: 10px 20px;
64
+ /* Get started: */
65
+ font-family: helvetica, arial, sans-serif;
66
+ font-size: 12px;
67
+ font-weight: 550;
68
+ color: #FFFFFF;
69
+ text-align: center;
70
+ text-decoration: none;
71
+ display: inline-block;
72
+ letter-spacing: 0.2px;
73
+ margin: 0px 12px 10px 0px;
74
+ width: 60px;
75
+ }
76
+
77
+ #fbsetup .btn.grey{
78
+ background: #f6f7f8;
79
+ border-color: #e0e1e2;
80
+ color: #4e5665;
81
+ width: 75px;
82
+ }
83
+
84
+ #fbsetup .btn.pre-setup{
85
+ font-size: 13px;
86
+ width: 80px;
87
+ }
88
+
89
+ #fbsetup header {
90
+ display: block;
91
+ background: url(../image/facebook.png) no-repeat #4267B2;
92
+ background-position: 20px center;
93
+ background-size: 100px 19px;
94
+ padding: 20px;
95
+ }
96
+ #fbsetup h1 {
97
+ /* title: */
98
+ font-family: arial;
99
+ font-size: 18px;
100
+ font-weight: bold;
101
+ color: #141823;
102
+ margin-top: 12px;
103
+ width: 400px;
104
+ }
105
+ #fbsetup p {
106
+ /* copy: */
107
+ font-family: Helvetica;
108
+ font-size: 12px;
109
+ color: #1D2129;
110
+ margin: 10px 0px 14px 0px;
111
+ }
112
+ #fbsetup h2 {
113
+ color: rgb(0,0,0);
114
+ font-size: 14px;
115
+ font-weight: normal;
116
+ line-height: 18px;
117
+ margin-bottom: 6px;
118
+ margin-top: 12px;
119
+ width: 520px;
120
+ }
121
+ #fbsetup ul {
122
+ list-style-type: disc;
123
+ margin: 10px 10px 15px 19px;
124
+ }
125
+ #fbsetup li {
126
+ margin: 2px 0px;
127
+ padding-left: 8px;
128
+ }
129
+ #fbsetup .nux-message {
130
+ box-sizing: border-box;
131
+ color: #fff;
132
+ overflow: hidden;
133
+ padding: 9px;
134
+ position: absolute;
135
+ text-align: left;
136
+ transform: translateZ(0);
137
+ transition: all 200ms cubic-bezier(.08,.52,.52,1);
138
+ z-index: 5;
139
+ }
140
+ #fbsetup .nux-message .nux-message-text {
141
+ background: #3578e5;
142
+ border-radius: 3px;
143
+ box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
144
+ box-sizing: border-box;
145
+ padding: 10px 34px 12px 10px;
146
+ white-space: normal;
147
+ min-width: 252px;
148
+ }
149
+ #fbsetup .nux-message .nux-message-close-btn {
150
+ cursor: pointer;
151
+ position: absolute;
152
+ right: 19px;
153
+ top: 19px;
154
+ height: 12px;
155
+ width: 12px;
156
+ font-style: normal;
157
+ color: #ccc;
158
+ }
159
+ #fbsetup .nux-message .nux-message-close-btn:hover {
160
+ color: #fff;
161
+ }
162
+ #fbsetup .nux-message .nux-message-arrow {
163
+ border-right: 9px solid #3578e5;
164
+ left: 0;
165
+ position: absolute;
166
+ border-bottom: 9px solid transparent;
167
+ border-top: 9px solid transparent;
168
+ margin-top: -9px;
169
+ top: 50%;
170
+ }
171
+ #fbsetup .settings-section {
172
+ display: inline-block;
173
+ min-width: 45%;
174
+ max-width: 45%;
175
+ margin-right: 8px;
176
+ vertical-align: top;
177
+ }
178
+ #fbsetup .settings-section h1 {
179
+ color: rgb(126,129,136);
180
+ font-size: 14px;
181
+ margin-top: 0px;
182
+ width: auto;
183
+ }
184
+ #fbsetup .settings-section h2 {
185
+ color: rgb(126,129,136);
186
+ font-size: 14px;
187
+ margin-top: 2px;
188
+ width: auto;
189
+ }
190
+ #fbsetup .settings-section .btn.small {
191
+ background: #f6f7f8;
192
+ border-color: #e0e1e2;
193
+ color: #4e5665;
194
+ height: 20px;
195
+ margin-top: 10px;
196
+ padding: 2px 6px;
197
+ width: auto;
198
+ }
199
+ .tooltip {
200
+ position: relative;
201
+ border-bottom: 1px dotted black;
202
+ /* container: */
203
+ border: 1px solid;
204
+ border-radius: 2px;
205
+ /* Launch Test: */
206
+ font-size: 12px;
207
+ font-weight: 550;
208
+ color: #FFFFFF;
209
+ text-align: center;
210
+ text-decoration: none;
211
+ display: inline-block;
212
+ letter-spacing: 0.2px;
213
+ margin: 0px 12px 10px 0px;
214
+ background: #f6f7f8;
215
+ border-color: #e0e1e2;
216
+ color: #4e5665;
217
+ height: 20px;
218
+ margin-top: 10px;
219
+ padding: 2px 6px;
220
+ width: auto;
221
+ }
222
+ .tooltip .tooltiptext {
223
+ color: black;
224
+ font-size: 13px;
225
+ padding: 5px 0;
226
+ text-align: justify;
227
+ visibility: hidden;
228
+ width: 400px;
229
+ /* Position the tooltip */
230
+ position: absolute;
231
+ z-index: 1;
232
+ top: 100%;
233
+ left: 50%;
234
+ margin-left: -60px;
235
+ }
236
+ .tooltip:hover .tooltiptext {
237
+ visibility: visible;
238
+ opacity: 1;
239
+ }
240
+ #fbsetup a {
241
+ text-decoration: none;
242
+ }
243
+
244
+ #fbAdvancedOptionsText {
245
+ color: rgb(69, 114, 169);
246
+ font-size: 14px;
247
+ font-family: Helvetica;
248
+ padding: 8px 0px 8px 0px;
249
+ }
250
+
251
+ #fbAdvancedOptions {
252
+ color: rgb(126,129,136);
253
+ font-size: 12px;
254
+ font-family: Helvetica;
255
+ display: none;
256
+ padding: 8px 4px 8px 4px;
257
+ }
258
+
259
+ #fbAdvancedOptions .autosyncSavedNotice {
260
+ color: green;
261
+ font-size: 10px;
262
+ display: none;
263
+ }
264
+
265
+ #fbAdvancedOptions div {
266
+ padding: 4px 4px 4px 4px;
267
+ }
268
+
269
+ .autosyncTime {
270
+ padding: 0px;
271
+ font-family: Helvetica;
272
+ font-size: 12px;
273
+ height: 18px;
274
+ }
assets/image/FB-f-Logo__blue_29.png ADDED
Binary file
assets/image/facebook.png ADDED
Binary file
assets/image/help-center.png ADDED
Binary file
assets/image/small-store.png ADDED
Binary file
assets/js/facebook-infobanner.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+ /*
10
+ * Ajax helper function.
11
+ * Takes optional payload for POST and optional callback.
12
+ */
13
+ function ajax(action, payload = null, callback = null, failcallback = null) {
14
+ var data = {
15
+ 'action': action,
16
+ };
17
+ if (payload){
18
+ for (var attrname in payload) { data[attrname] = payload[attrname]; }
19
+ }
20
+
21
+ // Since Wordpress 2.8 ajaxurl is always defined in admin header and
22
+ // points to admin-ajax.php
23
+ jQuery.post(ajaxurl, data, function(response) {
24
+ if(callback) {
25
+ callback(response);
26
+ }
27
+ }).fail(function(errorResponse){
28
+ if(failcallback) {
29
+ failcallback(errorResponse);
30
+ }
31
+ });
32
+ }
33
+
34
+ function fb_woo_infobanner_post_click(){
35
+ console.log("Woo infobanner post tip click!");
36
+ return ajax('ajax_woo_infobanner_post_click');
37
+ }
38
+
39
+ function fb_woo_infobanner_post_xout() {
40
+ console.log("Woo infobanner post tip xout!");
41
+ return ajax('ajax_woo_infobanner_post_xout');
42
+ }
assets/js/facebook-metabox.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+
10
+ /*
11
+ * Ajax helper function.
12
+ * Takes optional payload for POST and optional callback.
13
+ */
14
+ function ajax(action, payload = null, cb = null, failcb = null) {
15
+ var data = {
16
+ 'action': action,
17
+ };
18
+ if (payload){
19
+ for (var attrname in payload) { data[attrname] = payload[attrname]; }
20
+ }
21
+
22
+ // Since Wordpress 2.8 ajaxurl is always defined in admin header and
23
+ // points to admin-ajax.php
24
+ jQuery.post(ajaxurl, data, function(response) {
25
+ if(cb) {
26
+ cb(response);
27
+ }
28
+ }).fail(function(errorResponse){
29
+ if(failcb) {
30
+ failcb(errorResponse);
31
+ }
32
+ });
33
+ }
34
+
35
+ function fb_reset_product(wp_id) {
36
+ if(confirm('Resetting Facebook metadata will not remove this product from your shop. ' +
37
+ 'If you have duplicated another product and are trying to publish a new Facebook product, ' +
38
+ 'click OK to proceed. ' +
39
+ 'Otherwise, Facebook metadata will be restored when this product is updated again.')) {
40
+ var metadata = document.querySelector('#fb_metadata');
41
+ if(metadata) {
42
+ metadata.innerHTML =
43
+ "<b>This product is not yet synced to Facebook.</b>";
44
+ }
45
+ return ajax(
46
+ 'ajax_reset_single_fb_product',
47
+ {'wp_id': wp_id}
48
+ );
49
+ }
50
+ }
51
+
52
+ function fb_delete_product(wp_id) {
53
+ if(confirm('Are you sure you want to delete this product on Facebook? If you only want to "hide" the product, '+
54
+ 'uncheck the "Visible" checkbox and hit "Update". If you delete a product on Facebook and hit "Update" after, ' +
55
+ 'this product will be recreated. To permanently remove this product from Facebook, hit "OK" and close the window.'+
56
+ 'This will not delete the product from WooCommerce.')) {
57
+ var metadata = document.querySelector('#fb_metadata');
58
+ if(metadata) {
59
+ metadata.innerHTML =
60
+ "<b>This product is not yet synced to Facebook.</b>";
61
+ }
62
+ return ajax(
63
+ 'ajax_delete_fb_product',
64
+ {'wp_id': wp_id}
65
+ );
66
+ }
67
+ }
assets/js/facebook-products.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+
10
+ /*
11
+ * Ajax helper function.
12
+ * Takes optional payload for POST and optional callback.
13
+ */
14
+ function ajax(action, payload = null, cb = null, failcb = null) {
15
+ var data = {
16
+ 'action': action,
17
+ };
18
+ if (payload){
19
+ for (var attrname in payload) { data[attrname] = payload[attrname]; }
20
+ }
21
+
22
+ // Since Wordpress 2.8 ajaxurl is always defined in admin header and
23
+ // points to admin-ajax.php
24
+ jQuery.post(ajaxurl, data, function(response) {
25
+ if(cb) {
26
+ cb(response);
27
+ }
28
+ }).fail(function(errorResponse){
29
+ if(failcb) {
30
+ failcb(errorResponse);
31
+ }
32
+ });
33
+ }
34
+
35
+ function fb_toggle_visibility(wp_id, published) {
36
+ var buttonId = document.querySelector("#viz_" + wp_id);
37
+ var tooltip = document.querySelector("#tip_" + wp_id);
38
+
39
+ if(published){
40
+ tooltip.setAttribute('data-tip',
41
+ 'Product is synced and published (visible) on Facebook.'
42
+ );
43
+ buttonId.setAttribute('onclick','fb_toggle_visibility('+wp_id+', false)');
44
+ buttonId.innerHTML = 'Hide';
45
+ buttonId.setAttribute('class', 'button');
46
+ } else {
47
+ tooltip.setAttribute('data-tip',
48
+ 'Product is synced but not marked as published (visible) on Facebook.'
49
+ );
50
+ buttonId.setAttribute('onclick','fb_toggle_visibility('+wp_id+', true)');
51
+ buttonId.innerHTML = 'Show';
52
+ buttonId.setAttribute('class', 'button button-primary button-large');
53
+ }
54
+
55
+ //Reset tooltip
56
+ jQuery(function($) {
57
+ $('.tips').tipTip({
58
+ 'attribute': 'data-tip',
59
+ 'fadeIn': 50,
60
+ 'fadeOut': 50,
61
+ 'delay': 200
62
+ });
63
+ });
64
+
65
+ return ajax(
66
+ 'ajax_fb_toggle_visibility',
67
+ {'wp_id': wp_id, 'published': published}
68
+ );
69
+ }
assets/js/facebook-settings.js ADDED
@@ -0,0 +1,882 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
3
+ *
4
+ * This source code is licensed under the license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @package FacebookCommerce
8
+ */
9
+
10
+ var fb_sync_no_response_count = 0;
11
+ var fb_show_advanced_options = false;
12
+
13
+ function toggleAdvancedOptions() {
14
+ var opts = document.getElementById("fbAdvancedOptions");
15
+ if (!fb_show_advanced_options) {
16
+ opts.style.display = "block";
17
+ document.getElementById('fbAdvancedOptionsText').innerHTML = 'Hide Advanced Settings';
18
+ } else {
19
+ opts.style.display = "none";
20
+ document.getElementById('fbAdvancedOptionsText').innerHTML = 'Show Advanced Settings';
21
+ }
22
+ fb_show_advanced_options = !fb_show_advanced_options;
23
+ }
24
+
25
+ function openPopup() {
26
+ var width = 1153;
27
+ var height = 808;
28
+ var topPos = screen.height / 2 - height / 2;
29
+ var leftPos = screen.width / 2 - width / 2;
30
+ window.originParam = window.location.protocol + '//' + window.location.host;
31
+ var popupUrl;
32
+ if(window.facebookAdsToolboxConfig.popupOrigin.includes('staticxx')) {
33
+ window.facebookAdsToolboxConfig.popupOrigin = 'https://www.facebook.com/';
34
+ }
35
+ window.facebookAdsToolboxConfig.popupOrigin = prepend_protocol(
36
+ window.facebookAdsToolboxConfig.popupOrigin
37
+ );
38
+ popupUrl = window.facebookAdsToolboxConfig.popupOrigin;
39
+
40
+ var path = '/ads/dia';
41
+ var page = window.open(popupUrl + '/login.php?display=popup&next=' + encodeURIComponent(popupUrl + path + '?origin=' + window.originParam + ' &merchant_settings_id=' + window.facebookAdsToolboxConfig.diaSettingId), 'DiaWizard', ['toolbar=no', 'location=no', 'directories=no', 'status=no', 'menubar=no', 'scrollbars=no', 'resizable=no', 'copyhistory=no', 'width=' + width, 'height=' + height, 'top=' + topPos, 'left=' + leftPos].join(','));
42
+
43
+ return function (type, params) {
44
+ page.postMessage({
45
+ type: type,
46
+ params: params
47
+ }, window.facebookAdsToolboxConfig.popupOrigin);
48
+ };
49
+ }
50
+
51
+ function prepend_protocol(url) {
52
+ // Preprend https if the url begis with //www.
53
+ if (url.indexOf('//www.') === 0) {
54
+ url = 'https:' + url;
55
+ }
56
+ return url;
57
+ }
58
+
59
+ function get_product_catalog_id_box() {
60
+ return document.querySelector('#woocommerce_facebookcommerce_fb_product_catalog_id') || null;
61
+ }
62
+ function get_pixel_id_box() {
63
+ return document.querySelector('#woocommerce_facebookcommerce_fb_pixel_id') || null;
64
+ }
65
+ function get_pixel_use_pii_id_box() {
66
+ return document.querySelector('#woocommerce_facebookcommerce_fb_pixel_use_pii') || null;
67
+ }
68
+ function get_api_key_box() {
69
+ return document.querySelector('#woocommerce_facebookcommerce_fb_api_key') || null;
70
+ }
71
+ function get_page_id_box() {
72
+ return document.querySelector('#woocommerce_facebookcommerce_fb_page_id') || null;
73
+ }
74
+ function get_ems_id_box() {
75
+ return document.querySelector('#woocommerce_facebookcommerce_fb_external_merchant_settings_id') || null;
76
+ }
77
+
78
+ /*
79
+ * Ajax helper function.
80
+ * Takes optional payload for POST and optional callback.
81
+ */
82
+ function ajax(action, payload = null, callback = null, failcallback = null) {
83
+ var data = {
84
+ 'action': action,
85
+ };
86
+ if (payload){
87
+ for (var attrname in payload) { data[attrname] = payload[attrname]; }
88
+ }
89
+
90
+ // Since Wordpress 2.8 ajaxurl is always defined in admin header and
91
+ // points to admin-ajax.php
92
+ jQuery.post(ajaxurl, data, function(response) {
93
+ if(callback) {
94
+ callback(response);
95
+ }
96
+ }).fail(function(errorResponse){
97
+ if(failcallback) {
98
+ failcallback(errorResponse);
99
+ }
100
+ });
101
+ }
102
+
103
+ var settings = {'facebook_for_woocommerce' : 1};
104
+ var pixel_settings = {'facebook_for_woocommerce' : 1};
105
+
106
+ function facebookConfig() {
107
+ window.sendToFacebook = openPopup();
108
+ window.diaConfig = { 'clientSetup': window.facebookAdsToolboxConfig };
109
+ }
110
+
111
+ function fb_flush(){
112
+ console.log("Removing all FBIDs from all products!");
113
+ return ajax('ajax_reset_all_fb_products');
114
+ }
115
+
116
+ function sync_confirm(verbose = null) {
117
+ var msg = '';
118
+ switch (verbose) {
119
+ case 'fb_force_resync':
120
+ msg = 'Your products will now be resynced with Facebook, ' +
121
+ 'this may take some time.';
122
+ break;
123
+ case 'fb_test_product_sync':
124
+ msg = 'Launch Test?';
125
+ break;
126
+ default:
127
+ msg = 'Facebook for WooCommerce automatically syncs your products on ' +
128
+ 'create/update. Are you sure you want to force product resync? ' +
129
+ 'This will query all published products and may take some time. ' +
130
+ 'You only need to do this if your products are out of sync ' +
131
+ 'or some of your products did not sync.';
132
+ }
133
+ if(confirm(msg)) {
134
+ sync_all_products(
135
+ window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload,
136
+ verbose == 'fb_test_product_sync'
137
+ );
138
+ window.fb_sync_start_time = new Date().getTime();
139
+ }
140
+ }
141
+
142
+ // Launch the confirm dialog immediately if the param is in the URL.
143
+ if (window.location.href.includes("fb_force_resync")) {
144
+ window.onload = function() { sync_confirm("fb_force_resync"); };
145
+ } else if (window.location.href.includes("fb_test_product_sync")) {
146
+ // Test products sync by feed.
147
+ window.is_test = true;
148
+ window.onload = function() { sync_confirm("fb_test_product_sync"); };
149
+ }
150
+
151
+ function sync_all_products($using_feed = false, $is_test = false) {
152
+ if (get_product_catalog_id_box() && !get_product_catalog_id_box().value){
153
+ return;
154
+ }
155
+ if (get_api_key_box() && !get_api_key_box().value){
156
+ return;
157
+ }
158
+ console.log('Syncing all products!');
159
+ window.fb_connected = true;
160
+ sync_in_progress();
161
+ if ($using_feed) {
162
+ window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload = true;
163
+ window.feed_upload = true;
164
+ ping_feed_status_queue();
165
+ return $is_test ? ajax('ajax_test_sync_products_using_feed')
166
+ : ajax('ajax_sync_all_fb_products_using_feed');
167
+ } else {
168
+ return ajax('ajax_sync_all_fb_products');
169
+ }
170
+ }
171
+
172
+ // Reset all state
173
+ function delete_all_settings(callback = null, failcallback = null) {
174
+ if (get_product_catalog_id_box()) {
175
+ get_product_catalog_id_box().value = '';
176
+ }
177
+ if(get_pixel_id_box()) {
178
+ get_pixel_id_box().value = '';
179
+ }
180
+ if(get_pixel_use_pii_id_box()) {
181
+ get_pixel_use_pii_id_box().checked = false;
182
+ }
183
+ if(get_api_key_box()) {
184
+ get_api_key_box().value = '';
185
+ }
186
+ if(get_page_id_box()) {
187
+ get_page_id_box().value = '';
188
+ }
189
+ if(get_ems_id_box()) {
190
+ get_ems_id_box().value = '';
191
+ }
192
+
193
+ window.facebookAdsToolboxConfig.pixel.pixelId = '';
194
+ window.facebookAdsToolboxConfig.diaSettingId = '';
195
+
196
+ reset_buttons();
197
+ window.fb_connected = false;
198
+
199
+ console.log('Deleting all settings and removing all FBIDs!');
200
+ return ajax('ajax_delete_fb_settings', null, callback, failcallback);
201
+ }
202
+
203
+ // save_settings and save_settings_and_sync should only be called once
204
+ // after all variables are set up in the settings global variable
205
+ // if called multiple times, race conditions might occur
206
+ // ---
207
+ // It's also called again if the pixel id is ever changed or pixel pii is
208
+ // enabled or disabled.
209
+ function save_settings(callback = null, failcallback = null, localsettings = null){
210
+ if (!localsettings) {
211
+ localsettings = settings;
212
+ }
213
+ ajax('ajax_save_fb_settings', localsettings,
214
+ function(response){
215
+ if(callback) {
216
+ callback(response);
217
+ }
218
+ },
219
+ function(errorResponse){
220
+ if(failcallback) {
221
+ failcallback(errorResponse);
222
+ }
223
+ }
224
+ );
225
+ }
226
+
227
+ // save_settings wrapper for plugins as we do not need to:
228
+ // 1. sync products again after plugin is configured
229
+ // 2. check api_key, which is from facebook and is only necessary
230
+ // for following sync products
231
+ function save_settings_for_plugin(callback, failcallback) {
232
+ save_settings(
233
+ function(response){
234
+ if (response && response.includes('settings_saved')){
235
+ console.log(response);
236
+ callback(response);
237
+ } else {
238
+ console.log('Fail response on save_settings_and_sync');
239
+ failcallback(response);
240
+ }
241
+ },
242
+ function(errorResponse){
243
+ console.log('Ajax error while saving settings:' + JSON.stringify(errorResponse));
244
+ failcallback(errorResponse);
245
+ });
246
+ }
247
+
248
+ // see comments in save_settings function above
249
+ function save_settings_and_sync(message) {
250
+ if ('api_key' in settings){
251
+ save_settings(
252
+ function(response){
253
+ if (response && response.includes('settings_saved')){
254
+ console.log(response);
255
+ //Final acks
256
+ window.sendToFacebook('ack set pixel', message.params);
257
+ window.sendToFacebook('ack set page access token', message.params);
258
+ window.sendToFacebook('ack set merchant settings', message.params);
259
+ sync_all_products(true);
260
+ } else {
261
+ window.sendToFacebook('fail save_settings', response);
262
+ console.log('Fail response on save_settings_and_sync');
263
+ }
264
+ },
265
+ function(errorResponse){
266
+ console.log('Ajax error while saving settings:' + JSON.stringify(errorResponse));
267
+ window.sendToFacebook('fail save_settings_ajax', JSON.stringify(errorResponse));
268
+ }
269
+ );
270
+ }
271
+ }
272
+
273
+ //Reset buttons to brand new setup state
274
+ function reset_buttons(){
275
+ if(document.querySelector('#settings')){
276
+ document.querySelector('#settings').style.display = 'none';
277
+ }
278
+ if(document.querySelector('#cta_button')){
279
+ var cta_element = document.querySelector('#cta_button');
280
+ cta_element.innerHTML = 'Get Started';
281
+ cta_element.style['font-size'] = '13px';
282
+ cta_element.style.width = '80px';
283
+ cta_element.href = '#';
284
+ cta_element.onclick= function() { facebookConfig(); };
285
+ }
286
+ if(document.querySelector('#learnmore_button')){
287
+ document.querySelector('#learnmore_button').style.display = 'none';
288
+ }
289
+ if(document.querySelector('#setup_h1')) {
290
+ document.querySelector('#setup_h1').innerHTML =
291
+ 'Grow your business on Facebook';
292
+ }
293
+ if(document.querySelector('#setup_l1')){
294
+ document.querySelector('#setup_l1').innerHTML =
295
+ 'Easily install a tracking pixel';
296
+ }
297
+ if(document.querySelector('#setup_l2')){
298
+ document.querySelector('#setup_l2').innerHTML =
299
+ 'Upload your products and create a shop';
300
+ }
301
+ if(document.querySelector('#setup_l3')){
302
+ document.querySelector('#setup_l3').innerHTML =
303
+ 'Create dynamic ads with your products and pixel';
304
+ }
305
+ }
306
+
307
+ //Remove reset/settings buttons during product sync
308
+ function sync_in_progress(){
309
+ if(document.querySelector('#settings')){
310
+ document.querySelector('#settings').style.display = '';
311
+ }
312
+ if(document.querySelector('#connection_status')){
313
+ document.querySelector('#connection_status').style.display = '';
314
+ }
315
+ if(document.querySelector('#sync_complete')){
316
+ document.querySelector('#sync_complete').style.display = 'none';
317
+ }
318
+ //Get rid of all the buttons
319
+ if(document.querySelector('#setting_button')){
320
+ document.querySelector('#setting_button').style['pointer-events'] = 'none';
321
+ }
322
+ if(document.querySelector('#resync_products')) {
323
+ document.querySelector('#resync_products').style['pointer-events'] = 'none';
324
+ }
325
+ if(document.querySelector('#test_product_sync')) {
326
+ document.querySelector('#test_product_sync').style.display = 'none';
327
+ }
328
+ //Set a product sync status
329
+ if(document.querySelector('#sync_progress')){
330
+ document.querySelector('#sync_progress').innerHTML =
331
+ 'Syncing... Keep this browser open <br/>' +
332
+ 'Until sync is complete<br/>' +
333
+ '<div class="loader"></div>';
334
+ }
335
+ }
336
+
337
+ function sync_not_in_progress(){
338
+ // Reset to pre-setup state.
339
+ if(document.querySelector('#cta_button')){
340
+ var cta_element = document.querySelector('#cta_button');
341
+ cta_element.innerHTML = 'Create Ad';
342
+ cta_element.style['font-size'] = '12px';
343
+ cta_element.style.width = '60px';
344
+ if (window.facebookAdsToolboxConfig.diaSettingId) {
345
+ cta_element.onclick= function() {
346
+ window.open('https://www.facebook.com/ads/dia/redirect/?settings_id=' +
347
+ window.facebookAdsToolboxConfig.diaSettingId + '&version=2' +
348
+ '&entry_point=admin_panel');
349
+ };
350
+ } else {
351
+ cta_element.style['pointer-events'] = 'none';
352
+ }
353
+ }
354
+ if(document.querySelector('#learnmore_button')){
355
+ var learnmore_element = document.querySelector('#learnmore_button');
356
+ if (window.facebookAdsToolboxConfig.diaSettingId) {
357
+ learnmore_element.style.display = '';
358
+ }
359
+ }
360
+ if(document.querySelector('#setup_h1')) {
361
+ document.querySelector('#setup_h1').innerHTML =
362
+ 'Reach the right people and sell more products';
363
+ }
364
+ if(document.querySelector('#setup_l1')){
365
+ document.querySelector('#setup_l1').innerHTML =
366
+ 'Create an ad in a few steps';
367
+ }
368
+ if(document.querySelector('#setup_l2')){
369
+ document.querySelector('#setup_l2').innerHTML =
370
+ 'Use built-in best practice for online sales';
371
+ }
372
+ if(document.querySelector('#setup_l3')){
373
+ document.querySelector('#setup_l3').innerHTML =
374
+ 'Get reporting on sales and revenue';
375
+ }
376
+ if(document.querySelector('#settings')){
377
+ document.querySelector('#settings').style.display = '';
378
+ }
379
+ // Enable buttons.
380
+ if(document.querySelector('#setting_button')){
381
+ document.querySelector('#setting_button').style['pointer-events'] = 'auto';
382
+ }
383
+ if(document.querySelector('#resync_products')) {
384
+ document.querySelector('#resync_products').style ['pointer-events'] = 'auto';
385
+ }
386
+ // Remove sync progress.
387
+ if(document.querySelector('#sync_progress')){
388
+ document.querySelector('#sync_progress').innerHTML = '';
389
+ }
390
+ }
391
+
392
+ function not_connected(){
393
+ if(document.querySelector('#connection_status')){
394
+ document.querySelector('#connection_status').style.display = 'none';
395
+ }
396
+
397
+ if(document.querySelector('#setting_button')){
398
+ document.querySelector('#setting_button').style['pointer-events'] = 'auto';
399
+ }
400
+ if(document.querySelector('#resync_products')) {
401
+ document.querySelector('#resync_products').style['pointer-events'] = 'none';
402
+ }
403
+ if(document.querySelector('#sync_complete')) {
404
+ document.querySelector('#sync_complete').style.display = 'none';
405
+ }
406
+ if(document.querySelector('#sync_progress')){
407
+ document.querySelector('#sync_progress').innerHTML = '';
408
+ }
409
+ }
410
+
411
+ function addAnEventListener(obj,evt,func) {
412
+ if ('addEventListener' in obj){
413
+ obj.addEventListener(evt,func, false);
414
+ } else if ('attachEvent' in obj){//IE
415
+ obj.attachEvent('on'+evt,func);
416
+ }
417
+ }
418
+
419
+ function setMerchantSettings(message) {
420
+ if (!message.params.setting_id) {
421
+ console.error('Facebook Extension Error: got no setting_id', message.params);
422
+ window.sendToFacebook('fail set merchant settings', message.params);
423
+ return;
424
+ }
425
+ if(get_ems_id_box()){
426
+ get_ems_id_box().value = message.params.setting_id;
427
+ }
428
+
429
+ settings.external_merchant_settings_id = message.params.setting_id;
430
+
431
+ //Immediately set in case button is clicked again
432
+ window.facebookAdsToolboxConfig.diaSettingId = message.params.setting_id;
433
+ //Ack merchant settings happens after settings are saved
434
+ }
435
+
436
+ function setCatalog(message) {
437
+ if (!message.params.catalog_id) {
438
+ console.error('Facebook Extension Error: got no catalog_id', message.params);
439
+ window.sendToFacebook('fail set catalog', message.params);
440
+ return;
441
+ }
442
+ if(get_api_key_box()){
443
+ get_product_catalog_id_box().value = message.params.catalog_id;
444
+ }
445
+
446
+ settings.product_catalog_id = message.params.catalog_id;
447
+
448
+ window.sendToFacebook('ack set catalog', message.params);
449
+ }
450
+
451
+
452
+ function setPixel(message) {
453
+ if (!message.params.pixel_id) {
454
+ console.error('Facebook Ads Extension Error: got no pixel_id', message.params);
455
+ window.sendToFacebook('fail set pixel', message.params);
456
+ return;
457
+ }
458
+ if(get_pixel_id_box()){
459
+ get_pixel_id_box().value = message.params.pixel_id;
460
+ }
461
+
462
+ settings.pixel_id = message.params.pixel_id;
463
+ pixel_settings.pixel_id = settings.pixel_id;
464
+ if (message.params.pixel_use_pii !== undefined) {
465
+ if(get_pixel_use_pii_id_box()){
466
+ //!! will explicitly convert truthy/falsy values to a boolean
467
+ get_pixel_use_pii_id_box().checked = !!message.params.pixel_use_pii;
468
+ }
469
+ settings.pixel_use_pii = message.params.pixel_use_pii;
470
+ pixel_settings.pixel_use_pii = settings.pixel_use_pii;
471
+ }
472
+
473
+ // We need this to support changing the pixel id after setup.
474
+ save_settings(
475
+ function(response){
476
+ if (response && response.includes('settings_saved')){
477
+ window.sendToFacebook('ack set pixel', message.params);
478
+ } //may not get settings_saved if we try to save pixel before an API key
479
+ },
480
+ function(errorResponse){
481
+ console.log(errorResponse);
482
+ window.sendToFacebook('fail set pixel', errorResponse);
483
+ },
484
+ pixel_settings
485
+ );
486
+ }
487
+
488
+ function genFeed(message) {
489
+ //no-op
490
+ }
491
+
492
+ function setAccessTokenAndPageId(message) {
493
+ if (!message.params.page_token) {
494
+ console.error('Facebook Ads Extension Error: got no page_token',
495
+ message.params);
496
+ window.sendToFacebook('fail set page access token', message.params);
497
+ return;
498
+ }
499
+ /*
500
+ Set page_token here
501
+ */
502
+
503
+ if(get_api_key_box()){
504
+ get_api_key_box().value = message.params.page_token;
505
+ }
506
+
507
+ if(get_page_id_box()){
508
+ get_page_id_box().value = message.params.page_id;
509
+ }
510
+
511
+ settings.api_key = message.params.page_token;
512
+ settings.page_id = message.params.page_id;
513
+ //Ack token in "save_settings_and_sync" for final ack
514
+
515
+ window.facebookAdsToolboxConfig.tokenExpired = false;
516
+ if(document.querySelector('#token_text')){
517
+ document.querySelector('#token_text').innerHTML =
518
+ `<strong>Your API key has been updated.<br />
519
+ Please refresh the page.</strong>`;
520
+ }
521
+ }
522
+
523
+ function setMsgerChatSetup(data) {
524
+ if (data.hasOwnProperty('is_messenger_chat_plugin_enabled')) {
525
+ settings.is_messenger_chat_plugin_enabled =
526
+ data.is_messenger_chat_plugin_enabled;
527
+ }
528
+ if (data.hasOwnProperty('facebook_jssdk_version')) {
529
+ settings.facebook_jssdk_version =
530
+ data.facebook_jssdk_version;
531
+ }
532
+ if (data.hasOwnProperty('page_id')) {
533
+ settings.fb_page_id = data.page_id;
534
+ }
535
+
536
+ if (data.hasOwnProperty('customization')) {
537
+ var customization = data.customization;
538
+
539
+ if (customization.hasOwnProperty('greetingTextCode')) {
540
+ settings.msger_chat_customization_greeting_text_code =
541
+ customization.greetingTextCode;
542
+ }
543
+ if (customization.hasOwnProperty('locale')) {
544
+ settings.msger_chat_customization_locale =
545
+ customization.locale;
546
+ }
547
+ if (customization.hasOwnProperty('themeColorCode')) {
548
+ settings.msger_chat_customization_theme_color_code =
549
+ customization.themeColorCode;
550
+ }
551
+ }
552
+ }
553
+
554
+ function iFrameListener(event) {
555
+ // Fix for web.facebook.com
556
+ const origin = event.origin || event.originalEvent.origin;
557
+ if (origin != window.facebookAdsToolboxConfig.popupOrigin &&
558
+ urlFromSameDomain(origin, window.facebookAdsToolboxConfig.popupOrigin)) {
559
+ window.facebookAdsToolboxConfig.popupOrigin = origin;
560
+ }
561
+
562
+ switch (event.data.type) {
563
+ case 'reset':
564
+ delete_all_settings(function(res){
565
+ if(res && event.data.params) {
566
+ if(res === 'Settings Deleted'){
567
+ window.sendToFacebook('ack reset', event.data.params);
568
+ }else{
569
+ console.log(res);
570
+ alert(res);
571
+ }
572
+ }else {
573
+ console.log("Got no response from delete_all_settings");
574
+ }
575
+ },function(err){
576
+ console.error(err);
577
+ });
578
+ break;
579
+ case 'get dia settings':
580
+ window.sendToFacebook('dia settings', window.diaConfig);
581
+ break;
582
+ case 'set merchant settings':
583
+ setMerchantSettings(event.data);
584
+ break;
585
+ case 'set catalog':
586
+ setCatalog(event.data);
587
+ break;
588
+ case 'set pixel':
589
+ setPixel(event.data);
590
+ break;
591
+ case 'gen feed':
592
+ genFeed();
593
+ break;
594
+ case 'set page access token':
595
+ //Should be last message received
596
+ setAccessTokenAndPageId(event.data);
597
+ save_settings_and_sync(event.data);
598
+ break;
599
+ case 'set msger chat':
600
+ setMsgerChatSetup(event.data.params);
601
+ save_settings_for_plugin(
602
+ function(response) {
603
+ window.sendToFacebook('ack msger chat', event.data);
604
+ },
605
+ function(response) {
606
+ window.sendToFacebook('fail ack msger chat', event.data);
607
+ });
608
+ break;
609
+ }
610
+ }
611
+
612
+ addAnEventListener(window,'message',iFrameListener);
613
+
614
+ function urlFromSameDomain(url1, url2) {
615
+ if (!url1.startsWith('http') || !url2.startsWith('http')) {
616
+ return false;
617
+ }
618
+ var u1 = parseURL(url1);
619
+ var u2 = parseURL(url2);
620
+ var u1host = u1.host.replace(/^\w+\./, 'www.');
621
+ var u2host = u2.host.replace(/^\w+\./, 'www.');
622
+ return u1.protocol === u2.protocol && u1host === u2host;
623
+ }
624
+
625
+ function parseURL(url) {
626
+ var parser = document.createElement('a');
627
+ parser.href = url;
628
+ return parser;
629
+ }
630
+
631
+ // Only do pings for supporting older (pre 1.8) setups.
632
+ window.fb_pings =
633
+ (window.facebookAdsToolboxConfig.feed.hasClientSideFeedUpload) ?
634
+ null :
635
+ setInterval(function(){
636
+ console.log("Pinging queue...");
637
+ check_queues();
638
+ }, 10000);
639
+
640
+ function ping_feed_status_queue(count = 0) {
641
+ window.fb_feed_pings = setInterval(function() {
642
+ console.log('Pinging feed uploading queue...');
643
+ check_feed_upload_queue(count);
644
+ }, 30000*(1 << count));
645
+ }
646
+
647
+ function product_sync_complete(sync_progress_element){
648
+ sync_not_in_progress();
649
+ if(document.querySelector('#sync_complete')){
650
+ document.querySelector('#sync_complete').style.display = '';
651
+ }
652
+ if(sync_progress_element) {
653
+ sync_progress_element.innerHTML = '';
654
+ }
655
+ clearInterval(window.fb_pings);
656
+ }
657
+
658
+ function check_queues(){
659
+ ajax('ajax_fb_background_check_queue',
660
+ {"request_time": new Date().getTime()}, function(response){
661
+ if (window.feed_upload) {
662
+ clearInterval(window.fb_pings);
663
+ return;
664
+ }
665
+ var sync_progress_element = document.querySelector('#sync_progress');
666
+ var res = parse_response_check_connection(response);
667
+ if (!res) {
668
+ if (fb_sync_no_response_count++ > 5) {
669
+ clearInterval(window.fb_pings);
670
+ }
671
+ return;
672
+ }
673
+ fb_sync_no_response_count = 0;
674
+
675
+ if(res){
676
+ if(!res.background){
677
+ console.log("No background sync found, disabling pings");
678
+ clearInterval(window.fb_pings);
679
+ }
680
+
681
+ var processing = !!res.processing; //explicit boolean conversion
682
+ var remaining = res.remaining;
683
+ if(processing) {
684
+ if(sync_progress_element) {
685
+ sync_progress_element.innerHTML =
686
+ '<strong>Progress:</strong> ' + remaining + ' item' +
687
+ (remaining > 1 ? 's' : '') + ' remaining.';
688
+ }
689
+ if(remaining === 0){
690
+ product_sync_complete(sync_progress_element);
691
+ }
692
+ } else {
693
+ //Not processing, none remaining. Either long complete, or just completed
694
+ if(window.fb_sync_start_time && res.request_time){
695
+ var request_time = new Date(parseInt(res.request_time));
696
+ if(window.fb_sync_start_time > request_time){
697
+ //Old ping, do nothing.
698
+ console.log("OLD PING");
699
+ return;
700
+ }
701
+ }
702
+
703
+ if(remaining === 0){
704
+ product_sync_complete(sync_progress_element);
705
+ }
706
+ }
707
+ }
708
+ });
709
+ }
710
+
711
+ function parse_response_check_connection(res) {
712
+ if (res) {
713
+ console.log(res);
714
+ var response = res.substring(res.indexOf("{")); //Trim leading extra chars (rnrnr)
715
+ response = JSON.parse(response);
716
+ if(!response.connected && !window.fb_connected){
717
+ not_connected();
718
+ return null;
719
+ }
720
+ return response;
721
+ }
722
+ return null;
723
+ }
724
+
725
+ function check_feed_upload_queue(check_num) {
726
+ ajax('ajax_check_feed_upload_status', null, function(response) {
727
+ var sync_progress_element = document.querySelector('#sync_progress');
728
+ var res = parse_response_check_connection(response);
729
+ clearInterval(window.fb_feed_pings);
730
+ if (res) {
731
+ var status = res.status;
732
+ switch (status) {
733
+ case 'complete':
734
+ window.feed_upload = false;
735
+ if (window.is_test) {
736
+ display_test_result();
737
+ } else {
738
+ product_sync_complete(sync_progress_element);
739
+ }
740
+ break;
741
+ case 'in progress':
742
+ if (sync_progress_element) {
743
+ sync_progress_element.innerHTML =
744
+ 'Syncing... Keep this browser open <br/>' +
745
+ 'Until sync is complete<br/>';
746
+ }
747
+ ping_feed_status_queue(check_num+1);
748
+ break;
749
+ default:
750
+ sync_progress_element.innerHTML =
751
+ '<strong>Something wrong when uploading, please try again.</strong>';
752
+ window.feed_upload = false;
753
+ if (window.is_test) {
754
+ display_test_result();
755
+ }
756
+ }
757
+ }
758
+ });
759
+ }
760
+
761
+ function display_test_result() {
762
+ ajax('ajax_display_test_result', null, function(response) {
763
+ var sync_complete_element = document.querySelector('#sync_complete');
764
+ var sync_progress_element = document.querySelector('#sync_progress');
765
+ var res = parse_response_check_connection(response);
766
+ if (res) {
767
+ var status = res.pass;
768
+ switch (status) {
769
+ case 'true':
770
+ sync_not_in_progress();
771
+ if(sync_complete_element) {
772
+ sync_complete_element.style.display = '';
773
+ sync_complete_element.innerHTML =
774
+ '<strong>Status: </strong>Test Pass.';
775
+ }
776
+ if(sync_progress_element) {
777
+ sync_progress_element.innerHTML = '';
778
+ }
779
+ window.is_test = false;
780
+ break;
781
+ case 'in progress':
782
+ if (sync_progress_element) {
783
+ sync_progress_element.innerHTML =
784
+ '<strong>Integration test in progress...</strong>';
785
+ }
786
+ ping_feed_status_queue();
787
+ break;
788
+ default:
789
+ window.debug_info = res.debug_info + '<br/>' + res.stack_trace;
790
+ if(sync_complete_element) {
791
+ sync_complete_element.style.display = '';
792
+ sync_complete_element.innerHTML =
793
+ '<strong>Status: </strong>Test Fail.';
794
+ }
795
+ if (sync_progress_element) {
796
+ sync_progress_element.innerHTML = '';
797
+ }
798
+ if(document.querySelector('#debug_info')) {
799
+ document.querySelector('#debug_info').style.display = '';
800
+ }
801
+ window.is_test = false;
802
+ }
803
+ }
804
+ });
805
+ }
806
+
807
+ function show_debug_info() {
808
+ var stack_trace_element = document.querySelector('#stack_trace');
809
+ if(stack_trace_element) {
810
+ stack_trace_element.innerHTML = window.debug_info;
811
+ }
812
+ if(document.querySelector('#debug_info')) {
813
+ document.querySelector('#debug_info').style.display = 'none';
814
+ }
815
+ window.debug_info = '';
816
+ }
817
+
818
+ function fbe_init_nux_messages() {
819
+ var jQuery = window.jQuery;
820
+ jQuery(function() {
821
+ jQuery.each(jQuery('.nux-message'), function(_index, nux_msg) {
822
+ var nux_msg_elem = jQuery(nux_msg);
823
+ var targetid = nux_msg_elem.data('target');
824
+ var target_elem = jQuery('#' + targetid);
825
+ var t_pos = target_elem.position();
826
+ var t_half_height = target_elem.height() / 2;
827
+ var t_width = target_elem.outerWidth();
828
+ nux_msg_elem.css({
829
+ 'top': '' + Math.ceil(t_pos.top + t_half_height) + 'px',
830
+ 'left': '' + Math.ceil(t_pos.left + t_width) + 'px',
831
+ 'display': 'block'
832
+ });
833
+ jQuery('.nux-message-close-btn', nux_msg_elem).click(function() {
834
+ jQuery(nux_msg).hide();
835
+ });
836
+ });
837
+ });
838
+ }
839
+
840
+ function saveAutoSyncSchedule() {
841
+ var isChecked = document.getElementsByClassName('autosyncCheck')[0].checked;
842
+ var timebox = document.getElementsByClassName('autosyncTime')[0];
843
+ var button = document.getElementsByClassName('autosyncSaveButton')[0];
844
+ var saved = document.getElementsByClassName('autosyncSavedNotice')[0];
845
+
846
+ if (!isChecked) {
847
+ timebox.setAttribute('disabled', true);
848
+ } else {
849
+ timebox.removeAttribute('disabled');
850
+ saved.style.transition = '';
851
+ saved.style.opacity = 1;
852
+ // Fade out the small 'Saved' after 3 seconds.
853
+ setTimeout(function() {
854
+ saved.style.opacity = 0;
855
+ saved.style.transition = 'opacity 5s';}
856
+ ,3000);
857
+ }
858
+
859
+ ajax('ajax_schedule_force_resync', {"enabled": isChecked ? 1 : 0, "time": timebox.value});
860
+ }
861
+
862
+ function onSetDisableSyncOnDevEnvironment() {
863
+ var isChecked = document.getElementsByClassName('disableOnDevEnvironment')[0].checked;
864
+ ajax(
865
+ 'ajax_update_fb_option',
866
+ {
867
+ "option": "fb_disable_sync_on_dev_environment",
868
+ "option_value": isChecked ? 1 : 0
869
+ }
870
+ );
871
+ }
872
+
873
+ function syncShortDescription() {
874
+ var isChecked = document.getElementsByClassName('syncShortDescription')[0].checked;
875
+ ajax(
876
+ 'ajax_update_fb_option',
877
+ {
878
+ "option": "fb_sync_short_description",
879
+ "option_value": isChecked ? 1 : 0
880
+ }
881
+ );
882
+ }
changelog.txt ADDED
@@ -0,0 +1,399 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *** Facebook for WooCommerce Changelog ***
2
+ 2019-02-26 - Version 1.9.11
3
+ * changing contributor to facebook from facebook4woocommerce, so that
4
+ woo plugin will be shown under https://profiles.wordpress.org/facebook/#content-plugins
5
+ * adding changelog in readme.txt so that notifications will be sent for updates and
6
+ changelog will be shown under https://wordpress.org/plugins/facebook-for-woocommerce/#developers
7
+ * removing debug flags notice under facebook-for-woocommerce.php so that developers will
8
+ be able to debug with debug logs
9
+
10
+ 2019-02-11 - Version 1.9.10
11
+ * Add facebook support link, this will help merchants to reach out to facebook customer service.
12
+ * Make plugin wordpress compatible by removing woocommerce updater and removing woo_include
13
+
14
+ 2018-12-30 - Version 1.9.9
15
+ * Fix issue with missing file in v1.9.8
16
+ * Remove misleading content relating to Instagram which is not launched yet.
17
+
18
+ 2018-11-30 - Version 1.9.8
19
+ * Prevent Show/Hide button auto scroll.
20
+ * DIY entry field for FB product Image.
21
+ * Initial Support for Advanced Bulk Edit.
22
+ * Added Option to Use short description instead of description.
23
+ * Added checkbox for disable sync with FB for dev environment.
24
+ * Add New Advanced Option : Auto Force Resync on Schedule.
25
+ * Don't sync out of stock items to FB depending on Woo setting.
26
+ * Add custom price.
27
+ * Fix 502 Bad Gateway Error When Redirect.
28
+ * Sync composite product with calculating the price based on the price of sub-items.
29
+ * Advanced Options Toggle in Configuration Screen.
30
+
31
+ 2018-11-01 - Version 1.9.7
32
+ * Support messenger chat customization dialog
33
+ * Add Copyright header.
34
+ * Fix lowercasting product description.
35
+ * Fix Connect Woo AYMT Logic Flow and improve Logging.
36
+
37
+ 2018-09-21 - Version 1.9.6
38
+ * Update plugin description of new design for WooCommerce.
39
+ * Remove get_date() in Woocommerce plugin.
40
+ * Add External Action-to-Make Channel Support.
41
+
42
+ 2018-08-14 - Version 1.9.5
43
+ * Fix Subscription Event Injection
44
+
45
+ 2018-08-10 - Version 1.9.4
46
+ * Support Lead Gen Event - Contact Form 7
47
+ * Separate Redirect Entry Point Logging
48
+ * Fix undefined variable warnings.
49
+ * Add a Settings link to the plugin config page from the WP Plugins page.
50
+ * Adding filter to Pixel init for injection of fbq(consent, revoke) (GDPR)
51
+
52
+ 2018-08-01 - Version 1.9.3
53
+ * Add Edge Cases for Integration Test.
54
+ * Fix Undefined PHP Warning.
55
+ * Add subscribe event.
56
+ * Fix Unable to Change Pixel after Setup.
57
+ * Fix Integration Test Confirmation Dialog.
58
+
59
+ 2018-07-08 - Version 1.9.2
60
+ * Exclude Virtual Variation and Set Staging.
61
+ * Add Version Number for Logging.
62
+
63
+ 2018-06-22 - version 1.9.1
64
+ * Fix Page Name Extra Space .
65
+ * Remove Strange Box in Design.
66
+
67
+ 2018-06-15 - version 1.9.0
68
+ * Fix Performance Issue by Reusing Existing wp_query Object Contents.
69
+ * Rename Long Column Which Causes UI Issues for Users in Product Overview.
70
+ * Update Admin Notice Content.
71
+ * Fix Weird ID Error Message.
72
+ * Fix Product ID for Brand Attribute.
73
+ * Don't use Checkout URL for Unknown Product Types.
74
+ * Add Beta Integration Test for Syncing Products by Uploading Feed.
75
+ * Fix Pixel Fired on Related Products.
76
+ * Redesign Admin Panel.
77
+ * Add Integration Test Entry Point Button.
78
+
79
+ 2018-05-02 - version 1.8.7
80
+ * Fix PHP Error Due to product_brand Taxonomy Not Existing
81
+
82
+ 2018-04-26 - version 1.8.6
83
+ * Potential Fix for Compatibility with Enhanced ECommerce Plugin
84
+ * Reduce Fetch ID API Call for Hidden Products
85
+ * Support Product Bundles Extension
86
+ * Basic Support for WP All Import
87
+ * Fix Trashing and Deletion
88
+ * Fetch Brand and Support WooCommerce Brand Extension
89
+ * Remove warning for duplicate SKUs
90
+ * Remove upsell header on config page
91
+ * Update Info Banner Content
92
+ * Fix Warning When Deleting NON PRODUCT post
93
+ * WPML Support : Language Selector
94
+
95
+ 2018-04-17 - version 1.8.5
96
+ * Fix ViewContent event incorrectly firing with content_type 'product'
97
+ * Fix product group retailer id not matching ViewContent content id.
98
+ * Added logging and defensive code to debug #348
99
+ * Warning about Cart URL changes now clears with Force Resync
100
+
101
+ 2018-04-06 - version 1.8.4
102
+ * Fire AddToCart on Cart Viewed for shops which redirect to cart.
103
+ * Fix HTML AJAX comment inside script breaking HTML optimizers.
104
+ * Log product sync speed as a performance metric.
105
+
106
+ 2018-03-30 - version 1.8.3
107
+ * Fix Hidden Product Showing Up in Shop after Initial Sync.
108
+ * Hiding a variable product now hides the product on FB.
109
+ * Fix Variable Subscription Products Not Syncing Variants.
110
+ * Support Default Variant in Plugin via Graph API: Create and Update.
111
+ * Set Default Variant as Default Product in Feed.
112
+
113
+ 2018-03-20 - version 1.8.2
114
+ * Fix category column in feed.
115
+ * Update Force Resync to use a feed if feed was used for initial sync. (~15x faster)
116
+ * Fix all caps description products being rejected from feed.
117
+ * Fix gallery images for variable products not showing in feed.
118
+ * Fix multiple category error in feed upload.
119
+
120
+ 2018-02-27 - version 1.8.1
121
+ * Fix Upgrade TagName For Beta Version
122
+
123
+ 2018-02-14 - version 1.8.0
124
+ * Up to 15x Performance Improvement of Initial Product Sync by using a feed upload.
125
+ * Fix Subscription Product Bug Due to API Change.
126
+ * Fix Undefined Index Notices.
127
+ * Ensure jQuery is Loaded before Using it.
128
+ * Moved apply_filters from Constructor to wp_head Action.
129
+ * Remove Atlernative Pixel Basecode Fetching.
130
+ * [WordPress]Separate the Plugins to Different Directories
131
+
132
+ 2018-02-01 - version 1.7.11
133
+ * Fix permission error due to difference in data format between graphapi and feed uploading.
134
+
135
+ 2018-01-31 - version 1.7.10
136
+ * Disable Alternate Pixel Basecode fetching due to issues for some stores.
137
+ * Fix variation carry main description if variant description is empty.
138
+ * Add version check information.
139
+
140
+ 2018-01-30 - version 1.7.9
141
+ * Add Filter hook for other plugins to override pixel behavior.
142
+ * Fix 500 errors when saving settings.
143
+
144
+ 2018-01-25 - version 1.7.8
145
+ * Fixes WC_Facebookcommerce_Pixel reference error
146
+
147
+ 2018-01-22 - version 1.7.7
148
+ * Fix purchase event not firing for Stripe.
149
+ * Fix duplicate pixel issue for Initiate Checkout and Purchase events
150
+ * Pixel basecode is fetched from Facebook when setup is completed.
151
+ * Pixel proxy endpoints added for cases where pixel script cannot be loaded.
152
+
153
+ 2018-01-03 - version 1.7.6
154
+ * WordPress only plugin notice on missing Pixel ID.
155
+ * WordPress only plugin direct link to settings page from plugin page.
156
+
157
+ 2018-01-11 - version 1.7.5
158
+ * Fix auto-updater to upgrade facebook-for-woocommerce only.
159
+
160
+ 2018-01-05 - version 1.7.4
161
+ * Fix purchase event not firing for some payment types.
162
+ * Provide functionality to refresh API token when invalid.
163
+ * Added Quick Edit Compatibility.
164
+ * Fix Incompatibility with remove HTTP extension.
165
+ * Added Bulk Edit Compatibility.
166
+ * WPML compatibility: products in non-default language set to staging
167
+ * Add support for variable subscription products.
168
+
169
+ 2017-12-13 - version 1.7.3
170
+ * Fix security hole that would allow a logged in user without
171
+ manage_woocommerce permissions to toggle the fb_visibility of products.
172
+ * Handle shortcodes.
173
+ * Support product duplication.
174
+
175
+ 2017-11-30 - version 1.7.2
176
+ * Fix issue with get_plugin_data being called before it was loaded.
177
+ * Improve perf of info dialog.
178
+ * Move class loading and DB read into admin gate to fix site slowdown.
179
+
180
+ 2017-11-29 - version 1.7.0
181
+ * Enable auto-upgrading.
182
+ * Remove the extra content IDs to fix match rate issue.
183
+ * Only show 'Any' in attribute value string and show attribute name.
184
+ * Fix trash/restore products visibility on Facebook Shop.
185
+ * Fix deprecated function for variant description.
186
+ * Add hook and fix checkout url for external products.
187
+ * Clean HTML tag in product title.
188
+ * Add info dialog on WooCommerce report, settings and status page.
189
+
190
+ 2017-11-03 - version 1.6.6
191
+ * Solve race condition to avoid minus remaining number in syncing process.
192
+ * Fix sync of variant product description.
193
+ * Enable line breaks in product main description.
194
+ * Enable upsell message and redirect link visible for upgrading users.
195
+ * Improve upsell message content and style.
196
+
197
+ 2017-10-19 - version 1.6.5
198
+ * Fix unterminated div tag. Thanks @pwag42
199
+ * After 7 days, show a link to a new ads interface on the settings page.
200
+
201
+ 2017-10-04 - version 1.6.4
202
+ * Default to variant specific image as primary image for FB.
203
+ * Add a Checkbox to allow override to use the parent product image.
204
+ * Don't sync items which have zero price.
205
+ * Existing 0 price items will be marked invisible after Force-Resync.
206
+ * Support Syncing Bookable Items with 'Display Cost' set.
207
+ * Fix 'Update' and 'Publish' for Bookable Items.
208
+
209
+
210
+ 2017-10-03 - version 1.6.3
211
+ * Use Bookable price when regular price doesn't exist.
212
+ * Support Default Variations.
213
+ * Fix warning when generating attribute names.
214
+
215
+ 2017-09-28 - version 1.6.2
216
+ * Fix "Invalid Parameter" API error caused by invalid sale dates.
217
+ * Fix variable product unable to sync gallery images.
218
+ * Cache gallery image urls for variable products to reduce DB load.
219
+ * Fix exception during generation of some ViewCategory events.
220
+
221
+ 2017-09-21 - version 1.6.1
222
+ * Prevent save settings button for other WooCommerce plugins erasing FB settings.
223
+
224
+ 2017-09-15 - version 1.6.0
225
+ * Support sale start and end dates
226
+ * Include tax on sale price as needed
227
+ * Fix visibility toggle on the product page
228
+ * No longer publish hidden products
229
+ * Use variable products' attribute name, instead of slug name
230
+ * Fix encoding issue in Variation names
231
+ * Resolve W3 validation error caused by pixel code
232
+ * Correctly sync variants where the attribute is not specified or set to 'any'.
233
+ * Save Changes button no longer disappears in other settings pages
234
+ * Fix a JS warning caused by ViewCategory events by switching to trackCustom
235
+ * Fix quoted strings having unneeded slashes in the FB Description
236
+ * Fix Unicode encoding in Category Names. Thanks @jancinert.
237
+
238
+ 2017-09-05 - version 1.5.1
239
+ * Fix critical issue with ViewContent events not matching products.
240
+
241
+ 2017-09-05 - version 1.5.0
242
+ * Added support for generic WordPress installations (without WooCommerce)
243
+ * Added Search events
244
+ * Setting page for Pixel ID and for enabling advanced measurement
245
+ * Use featured image as primary image for variants, and variant images as additional images.
246
+
247
+ 2017-08-25 - version 1.4.6
248
+ * Fix issue where prices were rounded incorrectly
249
+
250
+ 2017-08-15 - version 1.4.5
251
+ * Prevent printed output from breaking the popupOrigin
252
+ * Add composer.json file
253
+
254
+ 2017-07-25 - version 1.4.4
255
+ * Remove duplicate and blank content ids in pixel fires.
256
+ * Fix warning when sale price is malformed.
257
+
258
+ 2017-07-26 - version 1.4.3
259
+ * Remove search event for admin panel searches (fix JS error preventing quick edit)
260
+ * Prevent search event from firing twice
261
+ * Add categories to items
262
+ * Add re-configure button back if merchant is locked out (e.g. due to password change)
263
+
264
+ 2017-06-20 - version 1.4.2
265
+ * Add custom Facebook description box
266
+ * Less yelling during product sync
267
+ * Remove deprecation warnings
268
+ * Fix RTL issue for JS loading (thanks Ariel!)
269
+ * Validate gender param to FB enum
270
+ * Fix product deletion for sub variants
271
+
272
+ 2017-06-15 - version 1.4.1
273
+ * Fix OOM/whitescreen of death error (constrain product count query to IDs)
274
+
275
+ 2017-06-05 - version 1.4.0
276
+ * Background sync! Products will now sync in the background if you keep your settings page open.
277
+ * Fix purchase pixel fires for product variants
278
+ * Fix state on hide/show buttons
279
+ * Fix issue with incorrect price rounding
280
+ * Delete FB metadata from product items as well as groups
281
+ * Add GPL license to repo
282
+
283
+ 2017-05-30 - version 1.3.3
284
+ * Use display prices for regular prices (includes VAT if used)
285
+ * Switch from checkboxes to buttons on Products page
286
+ * Add "Reset Facebook metadata" option for merchants that duplicate products
287
+ * Add "Delete on Facebook" option for products
288
+ * Only delete products when actually deleted (vs trashed)
289
+ * Allow pixel resetting after setup completion
290
+ * Disable plugin if WP_DEBUG_DISPLAY is detected (prevent PHP notice injection)
291
+
292
+ 2017-05-25 - version 1.3.2
293
+ * Allow Force Resync button to resume stalled product syncs.
294
+ * Handle WP_Error errors
295
+
296
+ 2017-05-23 - version 1.3.1
297
+ * Only use checkout urls if valid cart url is detected
298
+ * Set priority and namespacing for all ajax functions
299
+ * Workaround for staticxx domain redirects
300
+
301
+ 2017-05-15 - version 1.3.0
302
+ * Improve memory usage and remove OOM for large product catalogs
303
+ * Split variant methods for product groups and product items (fix mismatches)
304
+ * Make re-sync button work for updates as well as creates
305
+ * Fix issue with newly added variants breaking out of their product group
306
+ * Use default form values for variants if present
307
+ * Strip registered shortcodes from descriptions via strip_shortcodes
308
+ * Remove PHP warning about variable passed by reference
309
+ * Capture and save existing FBIDs on "Duplicate Retailer ID" error
310
+
311
+ 2017-05-11 - version 1.2.6
312
+ * Remove use of post guid for product URLs
313
+
314
+ 2017-05-06 - version 1.2.5
315
+ * Ajax lockdown: transmit fb-specific code and set priority on save_settings
316
+
317
+ 2017-05-05 - version 1.2.4
318
+ * Fix issue with pixel selection step freezing due to missing site url.
319
+
320
+ 2017-05-04 - version 1.2.3
321
+ * Workaround for servers that send additional characters on ajax responses
322
+
323
+ 2017-05-02 - version 1.2.2
324
+ * Fix issues with non-english variant labels
325
+ * Support gallery image urls
326
+
327
+ 2017-05-02 - version 1.2.1
328
+ * Use wc_get_cart_url to support custom cart URLs
329
+ * Fix logging bug for users without manage_woocommerce attempting to set up
330
+
331
+ 2017-04-27 - version 1.2.0
332
+ * Allow regular admins with manage_woocommerce permission to use plugin
333
+ * Lock settings during product sync
334
+ * Do not allow product sync during an existing product sync
335
+ * Validate catalog ID before product sync
336
+ * Fix issue with resync products button
337
+ * Fix issue with plugin breaking product search
338
+
339
+ 2017-04-25 - version 1.1.0
340
+ * Support duplicate SKUs via better retailer ID logic
341
+ * Add "resync products" button
342
+ * Update FB logging to support objects as well as strings
343
+ * Add support for regular and sale pricing
344
+
345
+ 2017-04-20 - version 1.0.3
346
+ * Remove delete button (use Advanced Settings tab instead)
347
+ * Cleaned up admin messages, prepended them with "Facebook for WooCommerce"
348
+
349
+ 2017-04-13 - version 1.0.2
350
+ * Remove Save Settings button when not in debug mode
351
+
352
+ 2017-04-11 - version 1.0.1
353
+ * Prevent blank item descriptions
354
+ * Update copy on setup page
355
+ * Sanitize settings before sending via fblog
356
+ * Show better warnings for duplicate SKUs
357
+
358
+ 2017-04-10 - version 1.0.0
359
+ * First release! Woo!
360
+
361
+ 2017-04-09 - version 0.8.0
362
+ * Include status messages during background sync
363
+ * Fix several PHP warnings and various WooCommerce 3.0 compatibility issues
364
+
365
+ 2017-04-07 - version 0.7.2
366
+ * Fix issue where products were not being created due to blank descriptions
367
+
368
+ 2017-04-04 - version 0.7.1
369
+ * Clean up output for checkboxes via printf
370
+ * Bug fixes and performance improvements
371
+
372
+ 2017-04-03 - version 0.7.0
373
+ * Add new product visibility checkbox toggle for Facebook Shops on Product list
374
+ * Official rename: Facebook for WooCommerce
375
+
376
+ 2017-03-30 - version 0.6.0
377
+ * Revert back to minor revisions for versioning
378
+ * Add ability to reset all settings to start over
379
+
380
+ 2017-03-20 - version 0.0.5
381
+ * Fix "Get Started" button bug
382
+ * Fix issue preventing "Use Advanced Matching" setting from properly saving
383
+ * Various security fixes
384
+
385
+ 2017-03-01 - version 0.0.4
386
+
387
+ * Add Facebook metabox on product page with products' FB ID and a Visibility toggle (publish/unpublish).
388
+
389
+ 2017-02-15 - version 0.0.3
390
+
391
+ * Add a change log!
392
+ * Modify plugin to check for both local and network installs of WooCommerce.
393
+ * Disable plugin hooks that were triggering before setup was complete.
394
+ * Escape HTML tags in product descriptions.
395
+ * Use attribute label name instead of first-letter-capitalized slug.
396
+
397
+ 2017-02-02 - version 0.0.2
398
+
399
+ * Fix breaking when debug mode is disabled (null check in facebook-settings.js)
composer.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "facebookincubator/facebook-for-woocommerce",
3
+ "description": "Grow your business with Facebook for WooCommerce! This plugin will install a Facebook Pixel and optionally create a shop on your Facebook page.",
4
+ "type": "wordpress-plugin",
5
+ "license": "GPL-2.0+",
6
+ "require": {
7
+ "composer/installers": "~1.0",
8
+ "php": ">=5.6"
9
+ },
10
+ "repositories": [
11
+ {
12
+ "type": "git",
13
+ "url" : "git@github.com:facebookincubator/facebook-for-woocommerce.git"
14
+ }
15
+ ]
16
+ }
facebook-commerce-events-tracker.php ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!class_exists('WC_Facebookcommerce_EventsTracker')) :
12
+
13
+ if (!class_exists('WC_Facebookcommerce_Utils')) {
14
+ include_once 'includes/fbutils.php';
15
+ }
16
+
17
+ if (!class_exists('WC_Facebookcommerce_Pixel')) {
18
+ include_once 'facebook-commerce-pixel-event.php';
19
+ }
20
+
21
+ class WC_Facebookcommerce_EventsTracker {
22
+ private $pixel;
23
+ private static $isEnabled = true;
24
+ const FB_PRIORITY_HIGH = 2;
25
+ const FB_PRIORITY_LOW = 11;
26
+
27
+ public function __construct($user_info) {
28
+ $this->pixel = new WC_Facebookcommerce_Pixel($user_info);
29
+
30
+ add_action('wp_head', array($this, 'apply_filters'));
31
+
32
+ // Pixel Tracking Hooks
33
+ add_action('wp_head',
34
+ array($this, 'inject_base_pixel'));
35
+ add_action('wp_footer',
36
+ array($this, 'inject_base_pixel_noscript'));
37
+ add_action('woocommerce_after_single_product',
38
+ array($this, 'inject_view_content_event'), self::FB_PRIORITY_HIGH);
39
+ add_action('woocommerce_after_shop_loop',
40
+ array($this, 'inject_view_category_event'));
41
+ add_action('pre_get_posts',
42
+ array($this, 'inject_search_event'));
43
+ add_action('woocommerce_after_cart',
44
+ array($this, 'inject_add_to_cart_redirect_event'));
45
+ add_action('woocommerce_add_to_cart',
46
+ array($this, 'inject_add_to_cart_event'), self::FB_PRIORITY_HIGH);
47
+ add_action('wc_ajax_fb_inject_add_to_cart_event',
48
+ array($this, 'inject_ajax_add_to_cart_event' ), self::FB_PRIORITY_HIGH);
49
+ add_action('woocommerce_after_checkout_form',
50
+ array($this, 'inject_initiate_checkout_event'));
51
+ add_action('woocommerce_thankyou',
52
+ array($this, 'inject_gateway_purchase_event'), self::FB_PRIORITY_HIGH);
53
+ add_action('woocommerce_payment_complete',
54
+ array($this, 'inject_purchase_event'), self::FB_PRIORITY_HIGH);
55
+ add_action('wpcf7_contact_form',
56
+ array($this, 'inject_lead_event_hook'), self::FB_PRIORITY_LOW);
57
+
58
+ }
59
+
60
+ public function apply_filters() {
61
+ self::$isEnabled = apply_filters(
62
+ "facebook_for_woocommerce_integration_pixel_enabled",
63
+ self::$isEnabled);
64
+ }
65
+
66
+ /**
67
+ * Base pixel code to be injected on page head. Because of this, it's better
68
+ * to echo the return value than using
69
+ * WC_Facebookcommerce_Utils::wc_enqueue_js() in this case
70
+ */
71
+ public function inject_base_pixel() {
72
+ if (self::$isEnabled) {
73
+ echo $this->pixel->pixel_base_code();
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Base pixel noscript to be injected on page body. This is to avoid W3
79
+ * validation error.
80
+ */
81
+ public function inject_base_pixel_noscript() {
82
+ if (self::$isEnabled) {
83
+ echo $this->pixel->pixel_base_code_noscript();
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Triggers ViewCategory for product category listings
89
+ */
90
+ public function inject_view_category_event() {
91
+ global $wp_query;
92
+ if (!self::$isEnabled) {
93
+ return;
94
+ }
95
+
96
+ $products = array_values(array_map(function($item) {
97
+ return wc_get_product($item->ID);
98
+ },
99
+ $wp_query->posts));
100
+
101
+ // if any product is a variant, fire the pixel with
102
+ // content_type: product_group
103
+ $content_type = 'product';
104
+ $product_ids = array();
105
+ foreach ($products as $product) {
106
+ if (!$product) {
107
+ continue;
108
+ }
109
+ $product_ids = array_merge(
110
+ $product_ids,
111
+ WC_Facebookcommerce_Utils::get_fb_content_ids($product));
112
+ if (WC_Facebookcommerce_Utils::is_variable_type($product->get_type())) {
113
+ $content_type = 'product_group';
114
+ }
115
+ }
116
+
117
+ $categories =
118
+ WC_Facebookcommerce_Utils::get_product_categories(get_the_ID());
119
+
120
+ $this->pixel->inject_event(
121
+ 'ViewCategory',
122
+ array(
123
+ 'content_name' => $categories['name'],
124
+ 'content_category' => $categories['categories'],
125
+ 'content_ids' => json_encode(array_slice($product_ids, 0, 10)),
126
+ 'content_type' => $content_type
127
+ ),
128
+ 'trackCustom');
129
+ }
130
+
131
+ /**
132
+ * Triggers Search for result pages (deduped)
133
+ */
134
+ public function inject_search_event() {
135
+ if (!self::$isEnabled) {
136
+ return;
137
+ }
138
+
139
+ if (!is_admin() && is_search() && get_search_query() !== '') {
140
+ if ($this->pixel->check_last_event('Search')) {
141
+ return;
142
+ }
143
+
144
+ if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
145
+ $this->actually_inject_search_event();
146
+ } else {
147
+ add_action('wp_head', array($this, 'actually_inject_search_event'), 11);
148
+ }
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Triggers Search for result pages
154
+ */
155
+ public function actually_inject_search_event() {
156
+ if (!self::$isEnabled) {
157
+ return;
158
+ }
159
+
160
+ $this->pixel->inject_event(
161
+ 'Search',
162
+ array(
163
+ 'search_string' => get_search_query()
164
+ ));
165
+ }
166
+
167
+ /**
168
+ * Helper function to iterate through a cart and gather all content ids
169
+ */
170
+ private function get_content_ids_from_cart($cart) {
171
+ $product_ids = array();
172
+ foreach ($cart as $item) {
173
+ $product_ids = array_merge(
174
+ $product_ids,
175
+ WC_Facebookcommerce_Utils::get_fb_content_ids($item['data']));
176
+ }
177
+ return $product_ids;
178
+ }
179
+
180
+ /**
181
+ * Triggers ViewContent product pages
182
+ */
183
+ public function inject_view_content_event() {
184
+ if (!self::$isEnabled) {
185
+ return;
186
+ }
187
+ global $post;
188
+ $product = wc_get_product($post->ID);
189
+ $content_type = 'product_group';
190
+ if (!$product) {
191
+ return;
192
+ }
193
+
194
+ // if product is a variant, fire the pixel with content_type: product_group
195
+ if (WC_Facebookcommerce_Utils::is_variation_type($product->get_type())) {
196
+ $content_type = 'product';
197
+ }
198
+
199
+ $content_ids = WC_Facebookcommerce_Utils::get_fb_content_ids($product);
200
+ $this->pixel->inject_event(
201
+ 'ViewContent',
202
+ array(
203
+ 'content_name' => $product->get_title(),
204
+ 'content_ids' => json_encode($content_ids),
205
+ 'content_type' => $content_type,
206
+ 'value' => $product->get_price(),
207
+ 'currency' => get_woocommerce_currency()
208
+ ));
209
+ }
210
+
211
+ /**
212
+ * Triggers AddToCart for cart page and add_to_cart button clicks
213
+ */
214
+ public function inject_add_to_cart_event() {
215
+ if (!self::$isEnabled) {
216
+ return;
217
+ }
218
+
219
+ $product_ids = $this->get_content_ids_from_cart(WC()->cart->get_cart());
220
+
221
+ $this->pixel->inject_event(
222
+ 'AddToCart',
223
+ array(
224
+ 'content_ids' => json_encode($product_ids),
225
+ 'content_type' => 'product',
226
+ 'value' => WC()->cart->total,
227
+ 'currency' => get_woocommerce_currency()
228
+ ));
229
+ }
230
+
231
+ /**
232
+ * Triggered by add_to_cart jquery trigger
233
+ */
234
+ public function inject_ajax_add_to_cart_event() {
235
+ if (!self::$isEnabled) {
236
+ return;
237
+ }
238
+
239
+ ob_start();
240
+
241
+ echo '<script>';
242
+
243
+ $product_ids = $this->get_content_ids_from_cart(WC()->cart->get_cart());
244
+
245
+ echo $this->pixel->build_event(
246
+ 'AddToCart',
247
+ array(
248
+ 'content_ids' => json_encode($product_ids),
249
+ 'content_type' => 'product',
250
+ 'value' => WC()->cart->total,
251
+ 'currency' => get_woocommerce_currency()
252
+ ));
253
+ echo '</script>';
254
+
255
+ $pixel = ob_get_clean();
256
+
257
+ wp_send_json($pixel);
258
+ }
259
+
260
+ /**
261
+ * Trigger AddToCart for cart page and woocommerce_after_cart hook.
262
+ * When set 'redirect to cart', ajax call for button click and
263
+ * woocommerce_add_to_cart will be skipped.
264
+ */
265
+ public function inject_add_to_cart_redirect_event() {
266
+ if (!self::$isEnabled) {
267
+ return;
268
+ }
269
+ $redirect_checked = get_option('woocommerce_cart_redirect_after_add', 'no');
270
+ if ($redirect_checked == 'yes') {
271
+ $this->inject_add_to_cart_event();
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Triggers InitiateCheckout for checkout page
277
+ */
278
+ public function inject_initiate_checkout_event() {
279
+ if (!self::$isEnabled ||
280
+ $this->pixel->check_last_event('InitiateCheckout')) {
281
+ return;
282
+ }
283
+
284
+ $product_ids = $this->get_content_ids_from_cart(WC()->cart->get_cart());
285
+
286
+ $this->pixel->inject_event(
287
+ 'InitiateCheckout',
288
+ array(
289
+ 'num_items' => WC()->cart->get_cart_contents_count(),
290
+ 'content_ids' => json_encode($product_ids),
291
+ 'content_type' => 'product',
292
+ 'value' => WC()->cart->total,
293
+ 'currency' => get_woocommerce_currency()
294
+ ));
295
+ }
296
+
297
+ /**
298
+ * Triggers Purchase for payment transaction complete and for the thank you
299
+ * page in cases of delayed payment.
300
+ */
301
+ public function inject_purchase_event($order_id) {
302
+ if (!self::$isEnabled ||
303
+ $this->pixel->check_last_event('Purchase')) {
304
+ return;
305
+ }
306
+
307
+ $this->inject_subscribe_event($order_id);
308
+
309
+ $order = new WC_Order($order_id);
310
+ $content_type = 'product';
311
+ $product_ids = array();
312
+ foreach ($order->get_items() as $item) {
313
+ $product = wc_get_product($item['product_id']);
314
+ $product_ids = array_merge(
315
+ $product_ids,
316
+ WC_Facebookcommerce_Utils::get_fb_content_ids($product));
317
+ if (WC_Facebookcommerce_Utils::is_variable_type($product->get_type())) {
318
+ $content_type = 'product_group';
319
+ }
320
+ }
321
+
322
+ $this->pixel->inject_event(
323
+ 'Purchase',
324
+ array(
325
+ 'content_ids' => json_encode($product_ids),
326
+ 'content_type' => $content_type,
327
+ 'value' => $order->get_total(),
328
+ 'currency' => get_woocommerce_currency()
329
+ ));
330
+ }
331
+
332
+ /**
333
+ * Triggers Subscribe for payment transaction complete of purchase with
334
+ * subscription.
335
+ */
336
+ public function inject_subscribe_event($order_id) {
337
+ if (!function_exists("wcs_get_subscriptions_for_order")) {
338
+ return;
339
+ }
340
+
341
+ $subscription_ids = wcs_get_subscriptions_for_order($order_id);
342
+ foreach ($subscription_ids as $subscription_id) {
343
+ $subscription = new WC_Subscription($subscription_id);
344
+ $this->pixel->inject_event(
345
+ 'Subscribe',
346
+ array(
347
+ 'sign_up_fee' => $subscription->get_sign_up_fee(),
348
+ 'value' => $subscription->get_total(),
349
+ 'currency' => get_woocommerce_currency()
350
+ ));
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Triggers Purchase for thank you page for COD, BACS CHEQUE payment
356
+ * which won't invoke woocommerce_payment_complete.
357
+ */
358
+ public function inject_gateway_purchase_event($order_id) {
359
+ if (!self::$isEnabled ||
360
+ $this->pixel->check_last_event('Purchase')) {
361
+ return;
362
+ }
363
+
364
+ $order = new WC_Order($order_id);
365
+ $payment = $order->get_payment_method();
366
+ $this->inject_purchase_event($order_id);
367
+ $this->inject_subscribe_event($order_id);
368
+ }
369
+
370
+ /** Contact Form 7 Support **/
371
+ public function inject_lead_event_hook() {
372
+ add_action('wp_footer', array($this, 'inject_lead_event'), 11);
373
+ }
374
+
375
+ public function inject_lead_event() {
376
+ if (!is_admin()) {
377
+ $this->pixel->inject_conditional_event(
378
+ 'Lead',
379
+ array(),
380
+ 'wpcf7submit',
381
+ '{ em: event.detail.inputs.filter(ele => ele.name.includes("email"))[0].value }');
382
+ }
383
+ }
384
+ }
385
+
386
+ endif;
facebook-commerce-messenger-chat.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!class_exists('WC_Facebookcommerce_MessengerChat')) :
12
+
13
+ if (!class_exists('WC_Facebookcommerce_Utils')) {
14
+ include_once 'includes/fbutils.php';
15
+ }
16
+
17
+ class WC_Facebookcommerce_MessengerChat {
18
+
19
+ public function __construct($settings) {
20
+ $this->enabled = isset($settings['is_messenger_chat_plugin_enabled'])
21
+ ? $settings['is_messenger_chat_plugin_enabled']
22
+ : 'no';
23
+
24
+ $this->page_id = isset($settings['fb_page_id'])
25
+ ? $settings['fb_page_id']
26
+ : '';
27
+
28
+ $this->jssdk_version = isset($settings['facebook_jssdk_version'])
29
+ ? $settings['facebook_jssdk_version']
30
+ : '';
31
+
32
+ $this->greeting_text_code = isset($settings['msger_chat_customization_greeting_text_code'])
33
+ ? $settings['msger_chat_customization_greeting_text_code']
34
+ : null;
35
+
36
+ $this->locale = isset($settings['msger_chat_customization_locale'])
37
+ ? $settings['msger_chat_customization_locale']
38
+ : null;
39
+
40
+ $this->theme_color_code = isset($settings['msger_chat_customization_theme_color_code'])
41
+ ? $settings['msger_chat_customization_theme_color_code']
42
+ : null;
43
+
44
+ add_action('wp_footer', array($this, 'inject_messenger_chat_plugin'));
45
+ }
46
+
47
+ public function inject_messenger_chat_plugin() {
48
+ if ($this->enabled === 'yes') {
49
+ echo sprintf("<div
50
+ attribution=\"fbe_woocommerce\"
51
+ class=\"fb-customerchat\"
52
+ page_id=\"%s\"
53
+ %s
54
+ %s
55
+ %s /></div>
56
+ <!-- Facebook JSSDK -->
57
+ <script>
58
+ window.fbAsyncInit = function() {
59
+ FB.init({
60
+ appId : '',
61
+ autoLogAppEvents : true,
62
+ xfbml : true,
63
+ version : '%s'
64
+ });
65
+ };
66
+
67
+ (function(d, s, id){
68
+ var js, fjs = d.getElementsByTagName(s)[0];
69
+ if (d.getElementById(id)) {return;}
70
+ js = d.createElement(s); js.id = id;
71
+ js.src = 'https://connect.facebook.net/%s/sdk/xfbml.customerchat.js';
72
+ fjs.parentNode.insertBefore(js, fjs);
73
+ }(document, 'script', 'facebook-jssdk'));
74
+ </script>
75
+ <div></div>",
76
+ $this->page_id,
77
+ $this->theme_color_code ? sprintf('theme_color="%s"', $this->theme_color_code) : '',
78
+ $this->greeting_text_code ? sprintf('logged_in_greeting="%s"', $this->greeting_text_code) : '',
79
+ $this->greeting_text_code ? sprintf('logged_out_greeting="%s"', $this->greeting_text_code) : '',
80
+ $this->jssdk_version,
81
+ $this->locale ? $this->locale : 'en_US');
82
+ }
83
+ }
84
+
85
+ }
86
+
87
+ endif;
facebook-commerce-pixel-event.php ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!class_exists('WC_Facebookcommerce_Pixel')) :
12
+
13
+
14
+ class WC_Facebookcommerce_Pixel {
15
+ const SETTINGS_KEY = 'facebook_config';
16
+ const PIXEL_ID_KEY = 'pixel_id';
17
+ const USE_PII_KEY = 'use_pii';
18
+
19
+ const PIXEL_RENDER = 'pixel_render';
20
+ const NO_SCRIPT_RENDER = 'no_script_render';
21
+
22
+ private $user_info;
23
+ private $last_event;
24
+ static $render_cache = array();
25
+
26
+ static $default_pixel_basecode = "
27
+ <script type='text/javascript'>
28
+ !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
29
+ n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
30
+ n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
31
+ t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
32
+ document,'script','https://connect.facebook.net/en_US/fbevents.js');
33
+ </script>
34
+ ";
35
+
36
+ public function __construct($user_info=array()) {
37
+ $this->user_info = $user_info;
38
+ $this->last_event = '';
39
+ }
40
+
41
+ public static function initialize() {
42
+ if (!is_admin()) {
43
+ return;
44
+ }
45
+
46
+ // Initialize PixelID in storage - this will only need to happen when the
47
+ // use is an admin
48
+ $pixel_id = self::get_pixel_id();
49
+ if (!WC_Facebookcommerce_Utils::is_valid_id($pixel_id) &&
50
+ class_exists('WC_Facebookcommerce_WarmConfig')) {
51
+ $fb_warm_pixel_id = WC_Facebookcommerce_WarmConfig::$fb_warm_pixel_id;
52
+
53
+ if (WC_Facebookcommerce_Utils::is_valid_id($fb_warm_pixel_id) &&
54
+ (int)$fb_warm_pixel_id == $fb_warm_pixel_id) {
55
+ $fb_warm_pixel_id = (string)$fb_warm_pixel_id;
56
+ self::set_pixel_id($fb_warm_pixel_id);
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Returns FB pixel code script part
63
+ */
64
+ public function pixel_base_code() {
65
+ $pixel_id = self::get_pixel_id();
66
+ if (
67
+ (
68
+ isset(self::$render_cache[self::PIXEL_RENDER]) &&
69
+ self::$render_cache[self::PIXEL_RENDER] === true
70
+ ) ||
71
+ !isset($pixel_id) ||
72
+ $pixel_id === 0
73
+ ) {
74
+ return;
75
+ }
76
+
77
+ self::$render_cache[self::PIXEL_RENDER] = true;
78
+ $params = self::add_version_info();
79
+
80
+ return sprintf("
81
+ <!-- %s Facebook Integration Begin -->
82
+ %s
83
+ <script>
84
+ %s
85
+ fbq('track', 'PageView', %s);
86
+
87
+ document.addEventListener('DOMContentLoaded', function() {
88
+ jQuery && jQuery(function($){
89
+ $('body').on('added_to_cart', function(event) {
90
+ // Ajax action.
91
+ $.get('?wc-ajax=fb_inject_add_to_cart_event', function(data) {
92
+ $('head').append(data);
93
+ });
94
+ });
95
+ });
96
+ }, false);
97
+
98
+ </script>
99
+ <!-- DO NOT MODIFY -->
100
+ <!-- %s Facebook Integration end -->
101
+ ",
102
+ WC_Facebookcommerce_Utils::getIntegrationName(),
103
+ self::get_basecode(),
104
+ $this->pixel_init_code(),
105
+ json_encode($params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT),
106
+ WC_Facebookcommerce_Utils::getIntegrationName());
107
+ }
108
+
109
+ /**
110
+ * Prevent double-fires by checking the last event
111
+ */
112
+ public function check_last_event($event_name) {
113
+ return $event_name === $this->last_event;
114
+ }
115
+
116
+ /**
117
+ * Preferred method to inject events in a page, normally you should use this
118
+ * instead of WC_Facebookcommerce_Pixel::build_event()
119
+ */
120
+ public function inject_event($event_name, $params, $method='track') {
121
+ $code = self::build_event($event_name, $params, $method);
122
+ $this->last_event = $event_name;
123
+
124
+ if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
125
+ WC_Facebookcommerce_Utils::wc_enqueue_js($code);
126
+ } else {
127
+ printf("
128
+ <!-- Facebook Pixel Event Code -->
129
+ <script>
130
+ %s
131
+ </script>
132
+ <!-- End Facebook Pixel Event Code -->
133
+ ",
134
+ $code);
135
+ }
136
+ }
137
+
138
+ public function inject_conditional_event(
139
+ $event_name, $params, $listener, $jsonified_pii = '') {
140
+ $code = self::build_event($event_name, $params, 'track');
141
+ $this->last_event = $event_name;
142
+
143
+ // Prepends fbq(...) with pii information to the injected code.
144
+ if ($jsonified_pii && get_option(self::SETTINGS_KEY)[self::USE_PII_KEY]) {
145
+ $this->user_info = '%s';
146
+ $code =
147
+ sprintf($this->pixel_init_code(), '" || '.$jsonified_pii.' || "').$code;
148
+ }
149
+
150
+ printf("
151
+ <!-- Facebook Pixel Event Code -->
152
+ <script>
153
+ document.addEventListener('%s', function (event) {
154
+ %s
155
+ }, false );
156
+ </script>
157
+ <!-- End Facebook Pixel Event Code -->
158
+ ",
159
+ $listener,
160
+ $code);
161
+ }
162
+
163
+ /**
164
+ * Returns FB pixel code noscript part to avoid W3 validation error
165
+ */
166
+ public function pixel_base_code_noscript() {
167
+ $pixel_id = self::get_pixel_id();
168
+ if (
169
+ (
170
+ isset(self::$render_cache[self::NO_SCRIPT_RENDER]) &&
171
+ self::$render_cache[self::NO_SCRIPT_RENDER] === true
172
+ ) ||
173
+ !isset($pixel_id) ||
174
+ $pixel_id === 0
175
+ ) {
176
+ return;
177
+ }
178
+
179
+ self::$render_cache[self::NO_SCRIPT_RENDER] = true;
180
+
181
+ return sprintf("
182
+ <!-- Facebook Pixel Code -->
183
+ <noscript>
184
+ <img height=\"1\" width=\"1\" style=\"display:none\" alt=\"fbpx\"
185
+ src=\"https://www.facebook.com/tr?id=%s&ev=PageView&noscript=1\"/>
186
+ </noscript>
187
+ <!-- DO NOT MODIFY -->
188
+ <!-- End Facebook Pixel Code -->
189
+ ",
190
+ esc_js($pixel_id));
191
+ }
192
+
193
+ /**
194
+ * You probably should use WC_Facebookcommerce_Pixel::inject_event() but
195
+ * this method is available if you need to modify the JS code somehow
196
+ */
197
+ public static function build_event($event_name, $params, $method='track') {
198
+ $params = self::add_version_info($params);
199
+ return sprintf(
200
+ "/* %s Facebook Integration Event Tracking */\n".
201
+ "fbq('%s', '%s', %s);",
202
+ WC_Facebookcommerce_Utils::getIntegrationName(),
203
+ $method,
204
+ $event_name,
205
+ json_encode($params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT));
206
+ }
207
+
208
+ public static function get_pixel_id() {
209
+ $fb_options = self::get_options();
210
+ if (!$fb_options) {
211
+ return '';
212
+ }
213
+ return isset($fb_options[self::PIXEL_ID_KEY]) ?
214
+ $fb_options[self::PIXEL_ID_KEY] : '';
215
+ }
216
+
217
+ public static function set_pixel_id($pixel_id) {
218
+ $fb_options = self::get_options();
219
+
220
+ if (isset($fb_options[self::PIXEL_ID_KEY])
221
+ && $fb_options[self::PIXEL_ID_KEY] == $pixel_id) {
222
+ return;
223
+ }
224
+
225
+ $fb_options[self::PIXEL_ID_KEY] = $pixel_id;
226
+ update_option(self::SETTINGS_KEY, $fb_options);
227
+ }
228
+
229
+ public static function get_basecode() {
230
+ return self::$default_pixel_basecode;
231
+ }
232
+
233
+ private static function get_version_info() {
234
+ global $wp_version;
235
+
236
+ if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
237
+ return array(
238
+ 'source' => 'woocommerce',
239
+ 'version' => WC()->version,
240
+ 'pluginVersion' => WC_Facebookcommerce_Utils::PLUGIN_VERSION
241
+ );
242
+ }
243
+
244
+ return array(
245
+ 'source' => 'wordpress',
246
+ 'version' => $wp_version,
247
+ 'pluginVersion' => WC_Facebookcommerce_Utils::PLUGIN_VERSION
248
+ );
249
+ }
250
+
251
+ public static function get_options() {
252
+ return get_option(self::SETTINGS_KEY, array(
253
+ self::PIXEL_ID_KEY => '0',
254
+ self::USE_PII_KEY => 0,
255
+ ));
256
+ }
257
+
258
+ /**
259
+ * Returns an array with version_info for pixel fires. Parameters provided by
260
+ * users should not be overwritten by this function
261
+ */
262
+ private static function add_version_info($params=array()) {
263
+ // if any parameter is passed in the pixel, do not overwrite it
264
+ return array_replace(self::get_version_info(), $params);
265
+ }
266
+
267
+ /**
268
+ * Init code might contain additional information to help matching website
269
+ * users with facebook users. Information is hashed in JS side using SHA256
270
+ * before sending to Facebook.
271
+ */
272
+ private function pixel_init_code() {
273
+ $version_info = self::get_version_info();
274
+ $agent_string = sprintf(
275
+ '%s-%s-%s',
276
+ $version_info['source'],
277
+ $version_info['version'],
278
+ $version_info['pluginVersion']);
279
+
280
+ $params = array(
281
+ 'agent' => $agent_string);
282
+
283
+ return apply_filters('facebook_woocommerce_pixel_init', sprintf(
284
+ "fbq('init', '%s', %s, %s);\n",
285
+ esc_js(self::get_pixel_id()),
286
+ json_encode($this->user_info, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT),
287
+ json_encode($params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT)));
288
+ }
289
+
290
+ }
291
+
292
+ endif;
facebook-commerce.php ADDED
@@ -0,0 +1,2541 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!defined('ABSPATH')) exit; // Exit if accessed directly
12
+
13
+ include_once('facebook-config-warmer.php');
14
+ include_once('includes/fbproduct.php');
15
+ include_once 'facebook-commerce-pixel-event.php';
16
+
17
+ class WC_Facebookcommerce_Integration extends WC_Integration {
18
+
19
+ const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
20
+ const FB_PRODUCT_ITEM_ID = 'fb_product_item_id';
21
+ const FB_PRODUCT_DESCRIPTION = 'fb_product_description';
22
+
23
+ const FB_VISIBILITY = 'fb_visibility';
24
+
25
+ const FB_CART_URL = 'fb_cart_url';
26
+
27
+ const FB_MESSAGE_DISPLAY_TIME = 180;
28
+
29
+ // Number of days to query tip.
30
+ const FB_TIP_QUERY = 1;
31
+
32
+ const FB_VARIANT_IMAGE = 'fb_image';
33
+
34
+ const FB_ADMIN_MESSAGE_PREPEND = '<b>Facebook for WooCommerce</b><br/>';
35
+
36
+ const FB_SYNC_IN_PROGRESS = 'fb_sync_in_progress';
37
+ const FB_SYNC_REMAINING = 'fb_sync_remaining';
38
+ const FB_SYNC_TIMEOUT = 30;
39
+ const FB_PRIORITY_MID = 9;
40
+
41
+ private $test_mode = false;
42
+
43
+ public function init_settings() {
44
+ parent::init_settings();
45
+ }
46
+
47
+ public function init_pixel() {
48
+ WC_Facebookcommerce_Pixel::initialize();
49
+
50
+ // Migrate WC customer pixel_id from WC settings to WP options.
51
+ // This is part of a larger effort to consolidate all the FB-specific
52
+ // settings for all plugin integrations.
53
+ if (is_admin()) {
54
+ $pixel_id = WC_Facebookcommerce_Pixel::get_pixel_id();
55
+ $settings_pixel_id = isset($this->settings['fb_pixel_id']) ?
56
+ (string)$this->settings['fb_pixel_id'] : null;
57
+ if (
58
+ WC_Facebookcommerce_Utils::is_valid_id($settings_pixel_id) &&
59
+ (!WC_Facebookcommerce_Utils::is_valid_id($pixel_id) ||
60
+ $pixel_id != $settings_pixel_id
61
+ )
62
+ ) {
63
+ WC_Facebookcommerce_Pixel::set_pixel_id($settings_pixel_id);
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Init and hook in the integration.
70
+ *
71
+ * @access public
72
+ * @return void
73
+ */
74
+ public function __construct() {
75
+ if (!class_exists('WC_Facebookcommerce_REST_Controller')) {
76
+ include_once( 'includes/fbcustomapi.php' );
77
+ $this->customapi = new WC_Facebookcommerce_REST_Controller();
78
+ }
79
+
80
+ if (!class_exists('WC_Facebookcommerce_EventsTracker')) {
81
+ include_once 'facebook-commerce-events-tracker.php';
82
+ }
83
+
84
+ $this->id = 'facebookcommerce';
85
+ $this->method_title = __(
86
+ 'Facebook for WooCommerce',
87
+ 'facebook-for-commerce');
88
+ $this->method_description = __(
89
+ 'Facebook Commerce and Dynamic Ads (Pixel) Extension',
90
+ 'facebook-for-commerce');
91
+
92
+ // Load the settings.
93
+ $this->init_settings();
94
+
95
+ $this->page_id = isset($this->settings['fb_page_id'])
96
+ ? $this->settings['fb_page_id']
97
+ : '';
98
+
99
+ $this->api_key = isset($this->settings['fb_api_key'])
100
+ ? $this->settings['fb_api_key']
101
+ : '';
102
+
103
+ $pixel_id = WC_Facebookcommerce_Pixel::get_pixel_id();
104
+ if (!$pixel_id) {
105
+ $pixel_id = isset($this->settings['fb_pixel_id']) ?
106
+ $this->settings['fb_pixel_id'] : '';
107
+ }
108
+ $this->pixel_id = isset($pixel_id)
109
+ ? $pixel_id
110
+ : '';
111
+
112
+ $this->pixel_install_time = isset($this->settings['pixel_install_time'])
113
+ ? $this->settings['pixel_install_time']
114
+ : '';
115
+
116
+ $this->use_pii = isset($this->settings['fb_pixel_use_pii'])
117
+ && $this->settings['fb_pixel_use_pii'] === 'yes'
118
+ ? true
119
+ : false;
120
+
121
+ $this->product_catalog_id = isset($this->settings['fb_product_catalog_id'])
122
+ ? $this->settings['fb_product_catalog_id']
123
+ : '';
124
+
125
+ $this->external_merchant_settings_id =
126
+ isset($this->settings['fb_external_merchant_settings_id'])
127
+ ? $this->settings['fb_external_merchant_settings_id']
128
+ : '';
129
+
130
+ if (!class_exists('WC_Facebookcommerce_Utils')) {
131
+ include_once 'includes/fbutils.php';
132
+ }
133
+
134
+ WC_Facebookcommerce_Utils::$ems = $this->external_merchant_settings_id;
135
+
136
+ if (!class_exists('WC_Facebookcommerce_Graph_API')) {
137
+ include_once 'includes/fbgraph.php';
138
+ $this->fbgraph = new WC_Facebookcommerce_Graph_API($this->api_key);
139
+ }
140
+
141
+ WC_Facebookcommerce_Utils::$fbgraph = $this->fbgraph;
142
+ $this->feed_id = isset($this->settings['fb_feed_id'])
143
+ ? $this->settings['fb_feed_id']
144
+ : '';
145
+
146
+ // Hooks
147
+ if (is_admin()) {
148
+ $this->init_pixel();
149
+ $this->init_form_fields();
150
+ // Display an info banner for eligible pixel and user.
151
+ if ($this->external_merchant_settings_id
152
+ && $this->pixel_id
153
+ && $this->pixel_install_time) {
154
+ $should_query_tip =
155
+ WC_Facebookcommerce_Utils::check_time_cap(
156
+ get_option('fb_info_banner_last_query_time', ''),
157
+ self::FB_TIP_QUERY);
158
+ $last_tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
159
+
160
+ if ($should_query_tip || $last_tip_info) {
161
+ if (!class_exists('WC_Facebookcommerce_Info_Banner')) {
162
+ include_once 'includes/fbinfobanner.php';
163
+ }
164
+ WC_Facebookcommerce_Info_Banner::get_instance(
165
+ $this->external_merchant_settings_id,
166
+ $this->fbgraph,
167
+ $should_query_tip);
168
+ }
169
+ }
170
+
171
+ if (!class_exists('WC_Facebook_Integration_Test')) {
172
+ include_once 'includes/test/facebook-integration-test.php';
173
+ }
174
+ $integration_test = WC_Facebook_Integration_Test::get_instance($this);
175
+ $integration_test::$fbgraph = $this->fbgraph;
176
+
177
+ if (!$this->pixel_install_time && $this->pixel_id) {
178
+ $this->pixel_install_time = current_time('mysql');
179
+ $this->settings['pixel_install_time'] = $this->pixel_install_time;
180
+ update_option(
181
+ $this->get_option_key(),
182
+ apply_filters(
183
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
184
+ $this->settings));
185
+ }
186
+ add_action('admin_notices', array( $this, 'checks' ));
187
+ add_action('woocommerce_update_options_integration_facebookcommerce',
188
+ array($this, 'process_admin_options'));
189
+ add_action('admin_enqueue_scripts', array( $this, 'load_assets'));
190
+
191
+ add_action('wp_ajax_ajax_save_fb_settings',
192
+ array($this, 'ajax_save_fb_settings'), self::FB_PRIORITY_MID);
193
+
194
+ add_action('wp_ajax_ajax_delete_fb_settings',
195
+ array($this, 'ajax_delete_fb_settings'), self::FB_PRIORITY_MID);
196
+
197
+ add_action('wp_ajax_ajax_sync_all_fb_products',
198
+ array($this, 'ajax_sync_all_fb_products'), self::FB_PRIORITY_MID);
199
+
200
+ add_action('wp_ajax_ajax_sync_all_fb_products_using_feed',
201
+ array($this, 'ajax_sync_all_fb_products_using_feed'),
202
+ self::FB_PRIORITY_MID);
203
+
204
+ add_action('wp_ajax_ajax_check_feed_upload_status',
205
+ array($this, 'ajax_check_feed_upload_status'),
206
+ self::FB_PRIORITY_MID);
207
+
208
+ add_action('wp_ajax_ajax_reset_all_fb_products',
209
+ array($this, 'ajax_reset_all_fb_products'),
210
+ self::FB_PRIORITY_MID);
211
+ add_action('wp_ajax_ajax_display_test_result',
212
+ array($this, 'ajax_display_test_result'));
213
+
214
+ add_action('wp_ajax_ajax_schedule_force_resync',
215
+ array($this, 'ajax_schedule_force_resync'), self::FB_PRIORITY_MID);
216
+
217
+ add_action('wp_ajax_ajax_update_fb_option',
218
+ array($this, 'ajax_update_fb_option'), self::FB_PRIORITY_MID);
219
+
220
+ // Only load product processing hooks if we have completed setup.
221
+ if ($this->api_key && $this->product_catalog_id) {
222
+ add_action(
223
+ 'woocommerce_process_product_meta_simple',
224
+ array($this, 'on_simple_product_publish'),
225
+ 10, // Action priority
226
+ 1 // Args passed to on_product_publish (should be 'id')
227
+ );
228
+
229
+ add_action(
230
+ 'woocommerce_process_product_meta_variable',
231
+ array($this, 'on_variable_product_publish'),
232
+ 10, // Action priority
233
+ 1 // Args passed to on_product_publish (should be 'id')
234
+ );
235
+
236
+ add_action(
237
+ 'woocommerce_process_product_meta_booking',
238
+ array($this, 'on_simple_product_publish'),
239
+ 10, // Action priority
240
+ 1 // Args passed to on_product_publish (should be 'id')
241
+ );
242
+
243
+ add_action(
244
+ 'woocommerce_process_product_meta_external',
245
+ array($this, 'on_simple_product_publish'),
246
+ 10, // Action priority
247
+ 1 // Args passed to on_product_publish (should be 'id')
248
+ );
249
+
250
+ add_action(
251
+ 'woocommerce_process_product_meta_subscription',
252
+ array($this, 'on_product_publish'),
253
+ 10, // Action priority
254
+ 1 // Args passed to on_product_publish (should be 'id')
255
+ );
256
+
257
+ add_action(
258
+ 'woocommerce_process_product_meta_variable-subscription',
259
+ array($this, 'on_product_publish'),
260
+ 10, // Action priority
261
+ 1 // Args passed to on_product_publish (should be 'id')
262
+ );
263
+
264
+ add_action('woocommerce_process_product_meta_bundle',
265
+ array($this, 'on_product_publish'),
266
+ 10, // Action priority
267
+ 1 // Args passed to on_product_publish (should be 'id')
268
+ );
269
+
270
+ add_action(
271
+ 'woocommerce_product_quick_edit_save',
272
+ array($this, 'on_quick_and_bulk_edit_save'),
273
+ 10, // Action priority
274
+ 1 // Args passed to on_quick_and_bulk_edit_save ('product')
275
+ );
276
+
277
+ add_action(
278
+ 'woocommerce_product_bulk_edit_save',
279
+ array($this, 'on_quick_and_bulk_edit_save'),
280
+ 10, // Action priority
281
+ 1 // Args passed to on_quick_and_bulk_edit_save ('product')
282
+ );
283
+
284
+ add_action(
285
+ 'before_delete_post',
286
+ array($this, 'on_product_delete'),
287
+ 10,
288
+ 1);
289
+
290
+ add_action('add_meta_boxes', array($this, 'fb_product_metabox'), 10, 1);
291
+
292
+ add_filter('manage_product_posts_columns',
293
+ array($this, 'fb_product_columns'));
294
+ add_action('manage_product_posts_custom_column',
295
+ array( $this, 'fb_render_product_columns' ), 2);
296
+ add_action('transition_post_status',
297
+ array($this, 'fb_change_product_published_status'), 10, 3);
298
+
299
+ // Product data tab
300
+ add_filter('woocommerce_product_data_tabs',
301
+ array($this, 'fb_new_product_tab'));
302
+
303
+ add_action('woocommerce_product_data_panels',
304
+ array($this, 'fb_new_product_tab_content' ));
305
+
306
+ add_action('wp_ajax_ajax_fb_toggle_visibility',
307
+ array($this, 'ajax_toggle_visibility'));
308
+
309
+ add_action('wp_ajax_ajax_reset_single_fb_product',
310
+ array($this, 'ajax_reset_single_fb_product'));
311
+
312
+ add_action('wp_ajax_ajax_delete_fb_product',
313
+ array($this, 'ajax_delete_fb_product'));
314
+
315
+ add_filter('woocommerce_duplicate_product_exclude_meta',
316
+ array($this, 'fb_duplicate_product_reset_meta'));
317
+
318
+ add_action('pmxi_after_xml_import',
319
+ array($this, 'wp_all_import_compat'));
320
+
321
+ add_action('wp_ajax_wpmelon_adv_bulk_edit',
322
+ array($this, 'ajax_woo_adv_bulk_edit_compat'), self::FB_PRIORITY_MID);
323
+
324
+ // Used to remove the 'you need to resync' message.
325
+ if (isset($_GET['remove_sticky'])) {
326
+ $this->remove_sticky_message();
327
+ }
328
+
329
+ if (defined('ICL_LANGUAGE_CODE')) {
330
+ include_once('includes/fbwpml.php');
331
+ new WC_Facebook_WPML_Injector();
332
+ }
333
+
334
+ }
335
+ $this->load_background_sync_process();
336
+ }
337
+ // Must be outside of admin for cron to schedule correctly.
338
+ add_action('sync_all_fb_products_using_feed',
339
+ array($this, 'sync_all_fb_products_using_feed'),
340
+ self::FB_PRIORITY_MID);
341
+
342
+ if ($this->pixel_id) {
343
+ $user_info = WC_Facebookcommerce_Utils::get_user_info($this->use_pii);
344
+ $this->events_tracker = new WC_Facebookcommerce_EventsTracker($user_info);
345
+ }
346
+
347
+ if (isset($this->settings['is_messenger_chat_plugin_enabled']) &&
348
+ $this->settings['is_messenger_chat_plugin_enabled'] === 'yes') {
349
+ if (!class_exists('WC_Facebookcommerce_MessengerChat')) {
350
+ include_once 'facebook-commerce-messenger-chat.php';
351
+ }
352
+ $this->messenger_chat = new WC_Facebookcommerce_MessengerChat($this->settings);
353
+ }
354
+ }
355
+
356
+ public function load_background_sync_process() {
357
+ // Attempt to load background processing (Woo 3.x.x only)
358
+ include_once('includes/fbbackground.php');
359
+ if (class_exists('WC_Facebookcommerce_Background_Process')) {
360
+ if (!isset($this->background_processor)) {
361
+ $this->background_processor =
362
+ new WC_Facebookcommerce_Background_Process($this);
363
+ }
364
+ }
365
+ add_action('wp_ajax_ajax_fb_background_check_queue',
366
+ array($this, 'ajax_fb_background_check_queue'));
367
+ }
368
+
369
+ public function ajax_fb_background_check_queue() {
370
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('background check queue', true);
371
+ $request_time = null;
372
+ if (isset($_POST['request_time'])) {
373
+ $request_time = esc_js(sanitize_text_field($_POST['request_time']));
374
+ }
375
+ if ($this->settings['fb_api_key']) {
376
+ if (isset($this->background_processor)) {
377
+ $is_processing = $this->background_processor->handle_cron_healthcheck();
378
+ $remaining = $this->background_processor->get_item_count();
379
+ $response = array(
380
+ 'connected' => true,
381
+ 'background' => true,
382
+ 'processing' => $is_processing,
383
+ 'remaining' => $remaining,
384
+ 'request_time' => $request_time,
385
+ );
386
+ } else {
387
+ $response = array(
388
+ 'connected' => true,
389
+ 'background' => false,
390
+ );
391
+ }
392
+ } else {
393
+ $response = array(
394
+ 'connected' => false,
395
+ 'background' => false,
396
+ );
397
+ }
398
+ printf(json_encode($response));
399
+ wp_die();
400
+ }
401
+
402
+ public function fb_new_product_tab($tabs) {
403
+ $tabs['fb_commerce_tab'] = array(
404
+ 'label' => __('Facebook', 'facebook-for-woocommerce'),
405
+ 'target' => 'facebook_options',
406
+ 'class' => array( 'show_if_simple', 'show_if_variable' ),
407
+ );
408
+ return $tabs;
409
+ }
410
+
411
+ public function fb_new_product_tab_content() {
412
+ global $post;
413
+ $woo_product = new WC_Facebook_Product($post->ID);
414
+ $description = get_post_meta(
415
+ $post->ID,
416
+ self::FB_PRODUCT_DESCRIPTION,
417
+ true);
418
+
419
+ $price = get_post_meta(
420
+ $post->ID,
421
+ WC_Facebook_Product::FB_PRODUCT_PRICE,
422
+ true);
423
+
424
+ $image = get_post_meta(
425
+ $post->ID,
426
+ WC_Facebook_Product::FB_PRODUCT_IMAGE,
427
+ true);
428
+
429
+ $image_setting = null;
430
+ if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
431
+ $image_setting = $woo_product->get_use_parent_image();
432
+ }
433
+
434
+ // 'id' attribute needs to match the 'target' parameter set above
435
+ ?><div id='facebook_options' class='panel woocommerce_options_panel'><?php
436
+ ?><div class='options_group'><?php
437
+ woocommerce_wp_textarea_input(
438
+ array(
439
+ 'id' => self::FB_PRODUCT_DESCRIPTION,
440
+ 'label' => __('Facebook Description', 'facebook-for-woocommerce'),
441
+ 'desc_tip' => 'true',
442
+ 'description' => __(
443
+ 'Custom (plain-text only) description for product on Facebook. '.
444
+ 'If blank, product description will be used. ' .
445
+ 'If product description is blank, shortname will be used.',
446
+ 'facebook-for-woocommerce'),
447
+ 'cols' => 40,
448
+ 'rows' => 20,
449
+ 'value' => $description,
450
+ ));
451
+ woocommerce_wp_textarea_input(
452
+ array(
453
+ 'id' => WC_Facebook_Product::FB_PRODUCT_IMAGE,
454
+ 'label' => __('Facebook Product Image', 'facebook-for-woocommerce'),
455
+ 'desc_tip' => 'true',
456
+ 'description' => __(
457
+ 'Image URL for product on Facebook. Must be an absolute URL '.
458
+ 'e.g. https://...'.
459
+ 'This can be used to override the primary image that will be '.
460
+ 'used on Facebook for this product. If blank, the primary '.
461
+ 'product image in Woo will be used as the primary image on FB.',
462
+ 'facebook-for-woocommerce'),
463
+ 'cols' => 40,
464
+ 'rows' => 10,
465
+ 'value' => $image,
466
+ ));
467
+ woocommerce_wp_text_input(
468
+ array(
469
+ 'id' => WC_Facebook_Product::FB_PRODUCT_PRICE,
470
+ 'label' => __('Facebook Price (' .
471
+ get_woocommerce_currency_symbol() . ')', 'facebook-for-woocommerce'),
472
+ 'desc_tip' => 'true',
473
+ 'description' => __(
474
+ 'Custom price for product on Facebook. '.
475
+ 'Please enter in monetary decimal (.) format without thousand '.
476
+ 'separators and currency symbols. '.
477
+ 'If blank, product price will be used. ',
478
+ 'facebook-for-woocommerce'),
479
+ 'cols' => 40,
480
+ 'rows' => 60,
481
+ 'value' => $price,
482
+ ));
483
+ if ($image_setting !== null) {
484
+ woocommerce_wp_checkbox(array(
485
+ 'id' => self::FB_VARIANT_IMAGE,
486
+ 'label' => __('Use Parent Image', 'facebook-for-woocommerce'),
487
+ 'required' => false,
488
+ 'desc_tip' => 'true',
489
+ 'description' => __(
490
+ ' By default, the primary image uploaded to Facebook is the image'.
491
+ ' specified in each variant, if provided. '.
492
+ ' However, if you enable this setting, the '.
493
+ ' image of the parent will be used as the primary image'.
494
+ ' for this product and all its variants instead.'),
495
+ 'value' => $image_setting ? 'yes' : 'no',
496
+ ));
497
+ }
498
+ ?>
499
+ </div>
500
+ </div><?php
501
+ }
502
+
503
+ public function fb_product_columns($existing_columns) {
504
+ if (empty($existing_columns) && ! is_array($existing_columns)) {
505
+ $existing_columns = array();
506
+ }
507
+
508
+ $columns = array();
509
+ $columns['fb'] = __('FB Shop', 'facebook-for-woocommerce');
510
+
511
+ // Verify that cart URL hasn't changed. We do it here because this page
512
+ // is most likely to be visited (so it's a handy place to make the check)
513
+ $cart_url = get_option(self::FB_CART_URL);
514
+ if (!empty($cart_url) && (wc_get_cart_url() !== $cart_url)) {
515
+ $this->display_warning_message('One or more of your products is using a
516
+ checkout URL that may be different than your shop checkout URL.
517
+ <a href="' . WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL . '">
518
+ Re-sync your products to update checkout URLs on Facebook.</a>');
519
+ }
520
+
521
+ return array_merge($columns, $existing_columns);
522
+ }
523
+
524
+ public function fb_render_product_columns($column) {
525
+ global $post, $the_product;
526
+
527
+ wp_enqueue_script('wc_facebook_jsx', plugins_url(
528
+ '/assets/js/facebook-products.js?ts=' . time(), __FILE__));
529
+
530
+ if (empty($the_product) || $the_product->get_id() != $post->ID) {
531
+ $the_product = new WC_Facebook_Product($post);
532
+ }
533
+
534
+ if ($column === 'fb') {
535
+ $fb_product_group_id = $this->get_product_fbid(
536
+ self::FB_PRODUCT_GROUP_ID,
537
+ $post->ID,
538
+ $the_product);
539
+ if (!$fb_product_group_id) {
540
+ printf('<span>Not Synced</span>');
541
+ } else {
542
+ $viz_value = get_post_meta($post->ID, self::FB_VISIBILITY, true);
543
+ $data_tip = $viz_value === '' ?
544
+ 'Product is synced but not marked as published (visible)
545
+ on Facebook.' :
546
+ 'Product is synced and published (visible) on Facebook.';
547
+
548
+ printf('<span class="tips" id="tip_%1$s" data-tip="%2$s">',
549
+ $post->ID,
550
+ $data_tip);
551
+
552
+ if ($viz_value === '') {
553
+ printf(
554
+ '<a id="viz_%1$s" class="button button-primary button-large"
555
+ href="javascript:;" onclick="fb_toggle_visibility(%1$s, true)">Show</a>',
556
+ $post->ID);
557
+ } else {
558
+ printf(
559
+ '<a id="viz_%1$s" class="button" href="javascript:;"
560
+ onclick="fb_toggle_visibility(%1$s, false)">Hide</a>',
561
+ $post->ID);
562
+ }
563
+ }
564
+ }
565
+ }
566
+
567
+ public function fb_product_metabox() {
568
+ wp_enqueue_script('wc_facebook_jsx', plugins_url(
569
+ '/assets/js/facebook-metabox.js?ts=' . time(), __FILE__));
570
+
571
+ add_meta_box(
572
+ 'facebook_metabox', // Meta box ID
573
+ 'Facebook', // Meta box Title
574
+ array($this, 'fb_product_meta_box_html'), // Callback
575
+ 'product', // Screen to which to add the meta box
576
+ 'side' // Context
577
+ );
578
+ }
579
+
580
+ public function fb_product_meta_box_html() {
581
+ global $post;
582
+ $woo_product = new WC_Facebook_Product($post->ID);
583
+ $fb_product_group_id = $this->get_product_fbid(
584
+ self::FB_PRODUCT_GROUP_ID,
585
+ $post->ID,
586
+ $woo_product);
587
+ printf('<span id="fb_metadata">');
588
+ if ($fb_product_group_id) {
589
+ printf('Facebook ID: <a href="https://facebook.com/'.
590
+ $fb_product_group_id . '" target="_blank">' .
591
+ $fb_product_group_id . '</a><p/>');
592
+ if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
593
+ printf('<p>Variant IDs:<br/>');
594
+ $children = $woo_product->get_children();
595
+ foreach ($children as $child_id) {
596
+ $fb_product_item_id = $this->get_product_fbid(
597
+ self::FB_PRODUCT_ITEM_ID,
598
+ $child_id);
599
+ printf($child_id .' : <a href="https://facebook.com/'.
600
+ $fb_product_item_id . '" target="_blank">' .
601
+ $fb_product_item_id . '</a><br/>');
602
+ }
603
+ printf('</p>');
604
+ }
605
+
606
+ $checkbox_value = get_post_meta($post->ID, self::FB_VISIBILITY, true);
607
+
608
+ printf('Visible: <input name="%1$s" type="checkbox" value="1" %2$s/>',
609
+ self::FB_VISIBILITY,
610
+ $checkbox_value === '' ? '' : 'checked');
611
+ printf('<p/><input name="is_product_page" type="hidden" value="1"');
612
+
613
+ printf(
614
+ '<p/><a href="#" onclick="fb_reset_product(%1$s)">
615
+ Reset Facebook metadata</a>',
616
+ $post->ID);
617
+
618
+ printf(
619
+ '<p/><a href="#" onclick="fb_delete_product(%1$s)">
620
+ Delete product(s) on Facebook</a>',
621
+ $post->ID);
622
+ } else {
623
+ printf("<b>This product is not yet synced to Facebook.</b>");
624
+ }
625
+ printf('</span>');
626
+ }
627
+
628
+ private function get_product_count() {
629
+ $args = array(
630
+ 'post_type' => 'product',
631
+ 'post_status' => 'publish',
632
+ 'posts_per_page' => -1,
633
+ 'fields' => 'ids'
634
+ );
635
+ $products = new WP_Query($args);
636
+
637
+ wp_reset_postdata();
638
+
639
+ echo $products->found_posts;
640
+ }
641
+
642
+ /**
643
+ * Load DIA specific JS Data
644
+ */
645
+ public function load_assets() {
646
+ $screen = get_current_screen();
647
+
648
+ // load banner assets
649
+ wp_enqueue_script('wc_facebook_infobanner_jsx', plugins_url(
650
+ '/assets/js/facebook-infobanner.js?ts=' . time(), __FILE__));
651
+
652
+ wp_enqueue_style('wc_facebook_infobanner_css', plugins_url(
653
+ '/assets/css/facebook-infobanner.css', __FILE__));
654
+
655
+ if (strpos($screen->id , "page_wc-settings") == 0) {
656
+ return;
657
+ }
658
+
659
+ if (empty($_GET['tab'])) {
660
+ return;
661
+ }
662
+
663
+ if ('integration' !== $_GET['tab']) {
664
+ return;
665
+ }
666
+
667
+ ?>
668
+ <script>
669
+ window.facebookAdsToolboxConfig = {
670
+ hasGzipSupport:
671
+ '<?php echo extension_loaded('zlib') ? 'true' : 'false' ?>'
672
+ ,enabledPlugins: ['MESSENGER_CHAT','INSTAGRAM_SHOP']
673
+ ,enableSubscription:
674
+ '<?php echo class_exists('WC_Subscriptions') ? 'true' : 'false' ?>'
675
+ ,popupOrigin: '<?php echo isset($_GET['url']) ? esc_js($_GET['url']) :
676
+ 'https://www.facebook.com/' ?>'
677
+ ,feedWasDisabled: 'true'
678
+ ,platform: 'WooCommerce'
679
+ ,pixel: {
680
+ pixelId: '<?php echo $this->pixel_id ?: '' ?>'
681
+ ,advanced_matching_supported: true
682
+ }
683
+ ,diaSettingId: '<?php echo $this->external_merchant_settings_id ?: '' ?>'
684
+ ,store: {
685
+ baseUrl: window.location.protocol + '//' + window.location.host
686
+ ,baseCurrency:
687
+ '<?php echo esc_js(
688
+ WC_Admin_Settings::get_option('woocommerce_currency'))?>'
689
+ ,timezoneId: '<?php echo date('Z') ?>'
690
+ ,storeName:
691
+ '<?php echo esc_js(WC_Facebookcommerce_Utils::get_store_name()); ?>'
692
+ ,version: '<?php echo WC()->version ?>'
693
+ ,php_version: '<?php echo PHP_VERSION ?>'
694
+ ,plugin_version:
695
+ '<?php echo WC_Facebookcommerce_Utils::PLUGIN_VERSION ?>'
696
+ }
697
+ ,feed: {
698
+ totalVisibleProducts: '<?php echo $this->get_product_count() ?>'
699
+ ,hasClientSideFeedUpload: '<?php echo !!$this->feed_id ?>'
700
+ }
701
+ ,feedPrepared: {
702
+ feedUrl: ''
703
+ ,feedPingUrl: ''
704
+ ,samples: <?php echo $this->get_sample_product_feed()?>
705
+ }
706
+ ,tokenExpired: '<?php echo $this->settings['fb_api_key'] &&
707
+ !$this->get_page_name()?>'
708
+ };
709
+ </script>
710
+ <?php
711
+ wp_enqueue_script('wc_facebook_jsx', plugins_url(
712
+ '/assets/js/facebook-settings.js?ts=' . time(), __FILE__));
713
+ wp_enqueue_style('wc_facebook_css', plugins_url(
714
+ '/assets/css/facebook.css', __FILE__));
715
+ }
716
+
717
+ function on_product_delete($wp_id) {
718
+ $woo_product = new WC_Facebook_Product($wp_id);
719
+ if (!$woo_product->exists()) {
720
+ // This happens when the wp_id is not a product or it's already
721
+ // been deleted.
722
+ return;
723
+ }
724
+ $fb_product_group_id = $this->get_product_fbid(
725
+ self::FB_PRODUCT_GROUP_ID,
726
+ $wp_id,
727
+ $woo_product);
728
+ $fb_product_item_id = $this->get_product_fbid(
729
+ self::FB_PRODUCT_ITEM_ID,
730
+ $wp_id,
731
+ $woo_product);
732
+ if (! ($fb_product_group_id || $fb_product_item_id ) ) {
733
+ return; // No synced product, no-op.
734
+ }
735
+ $products = array($wp_id);
736
+ if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
737
+ $children = $woo_product->get_children();
738
+ $products = array_merge($products, $children);
739
+ }
740
+ foreach ($products as $item_id) {
741
+ $this->delete_product_item($item_id);
742
+ }
743
+ if ($fb_product_group_id) {
744
+ $pg_result = $this->fbgraph->delete_product_group($fb_product_group_id);
745
+ WC_Facebookcommerce_Utils::log($pg_result);
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Update FB visibility for trashing and restore.
751
+ */
752
+ function fb_change_product_published_status($new_status, $old_status, $post) {
753
+ global $post;
754
+ $visibility = $new_status == 'publish' ? 'published' : 'staging';
755
+
756
+ // change from publish status -> unpublish status, e.g. trash, draft, etc.
757
+ // change from trash status -> publish status
758
+ // no need to update for change from trash <-> unpublish status
759
+ if (($old_status == 'publish' && $new_status != 'publish') ||
760
+ ($old_status == 'trash' && $new_status == 'publish')) {
761
+ $this->update_fb_visibility($post->ID, $visibility);
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Generic function for use with any product publishing.
767
+ * Will determine product type (simple or variable) and delegate to
768
+ * appropriate handler.
769
+ */
770
+ function on_product_publish($wp_id) {
771
+ if (get_post_status($wp_id) != 'publish') {
772
+ return;
773
+ }
774
+
775
+ $woo_product = new WC_Facebook_Product($wp_id);
776
+ $product_type = $woo_product->get_type();
777
+ if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
778
+ $this->on_variable_product_publish($wp_id, $woo_product);
779
+ } else {
780
+ $this->on_simple_product_publish($wp_id, $woo_product);
781
+ }
782
+ }
783
+
784
+ /**
785
+ * If the user has opt-in to remove products that are out of stock,
786
+ * this function will delete the product from FB Page as well.
787
+ */
788
+ function delete_on_out_of_stock($wp_id, $woo_product) {
789
+ if (get_option('woocommerce_hide_out_of_stock_items') === 'yes' &&
790
+ !$woo_product->is_in_stock()) {
791
+ $this->delete_product_item($wp_id);
792
+ return true;
793
+ }
794
+ return false;
795
+ }
796
+
797
+ function on_variable_product_publish($wp_id, $woo_product = null) {
798
+ if (get_option('fb_disable_sync_on_dev_environment', false)) {
799
+ return;
800
+ }
801
+
802
+ if (get_post_status($wp_id) != 'publish') {
803
+ return;
804
+ }
805
+ // Check if product group has been published to FB. If not, it's new.
806
+ // If yes, loop through variants and see if product items are published.
807
+ if (!$woo_product) {
808
+ $woo_product = new WC_Facebook_Product($wp_id);
809
+ }
810
+
811
+ if ($this->delete_on_out_of_stock($wp_id, $woo_product)) {
812
+ return;
813
+ }
814
+
815
+ if (isset($_POST[self::FB_PRODUCT_DESCRIPTION])) {
816
+ $woo_product->set_description($_POST[self::FB_PRODUCT_DESCRIPTION]);
817
+ }
818
+ if (isset($_POST[WC_Facebook_Product::FB_PRODUCT_PRICE])) {
819
+ $woo_product->set_price($_POST[WC_Facebook_Product::FB_PRODUCT_PRICE]);
820
+ }
821
+ if (isset($_POST[WC_Facebook_Product::FB_PRODUCT_IMAGE])) {
822
+ $woo_product->set_product_image($_POST[WC_Facebook_Product::FB_PRODUCT_IMAGE]);
823
+ }
824
+
825
+ $woo_product->set_use_parent_image(
826
+ (isset($_POST[self::FB_VARIANT_IMAGE])) ?
827
+ $_POST[self::FB_VARIANT_IMAGE] :
828
+ null);
829
+ $fb_product_group_id = $this->get_product_fbid(
830
+ self::FB_PRODUCT_GROUP_ID,
831
+ $wp_id,
832
+ $woo_product);
833
+
834
+ if ($fb_product_group_id) {
835
+ $woo_product->update_visibility(
836
+ isset($_POST['is_product_page']),
837
+ isset($_POST[self::FB_VISIBILITY]));
838
+ $this->update_product_group($woo_product);
839
+ $child_products = $woo_product->get_children();
840
+ $variation_id = $woo_product->find_matching_product_variation();
841
+ // check if item_id is default variation. If yes, update in the end.
842
+ // If default variation value is to update, delete old fb_product_item_id
843
+ // and create new one in order to make it order correctly.
844
+ foreach ($child_products as $item_id) {
845
+ $fb_product_item_id =
846
+ $this->on_simple_product_publish($item_id, null, $woo_product);
847
+ if ($item_id == $variation_id && $fb_product_item_id) {
848
+ $this->set_default_variant($fb_product_group_id, $fb_product_item_id);
849
+ }
850
+ }
851
+ } else {
852
+ $this->create_product_variable($woo_product);
853
+ }
854
+ }
855
+
856
+ function on_simple_product_publish(
857
+ $wp_id,
858
+ $woo_product = null,
859
+ &$parent_product = null) {
860
+ if (get_option('fb_disable_sync_on_dev_environment', false)) {
861
+ return;
862
+ }
863
+
864
+ if (get_post_status($wp_id) != 'publish') {
865
+ return;
866
+ }
867
+
868
+ if (!$woo_product) {
869
+ $woo_product = new WC_Facebook_Product($wp_id, $parent_product);
870
+ }
871
+
872
+ if ($this->delete_on_out_of_stock($wp_id, $woo_product)) {
873
+ return;
874
+ }
875
+
876
+ if (isset($_POST[self::FB_PRODUCT_DESCRIPTION])) {
877
+ $woo_product->set_description($_POST[self::FB_PRODUCT_DESCRIPTION]);
878
+ }
879
+
880
+ if (isset($_POST[WC_Facebook_Product::FB_PRODUCT_PRICE])) {
881
+ $woo_product->set_price($_POST[WC_Facebook_Product::FB_PRODUCT_PRICE]);
882
+ }
883
+
884
+ if (isset($_POST[WC_Facebook_Product::FB_PRODUCT_IMAGE])) {
885
+ $woo_product->set_product_image($_POST[WC_Facebook_Product::FB_PRODUCT_IMAGE]);
886
+ }
887
+
888
+ // Check if this product has already been published to FB.
889
+ // If not, it's new!
890
+ $fb_product_item_id = $this->get_product_fbid(
891
+ self::FB_PRODUCT_ITEM_ID,
892
+ $wp_id,
893
+ $woo_product);
894
+
895
+ if ($fb_product_item_id) {
896
+ $woo_product->update_visibility(
897
+ isset($_POST['is_product_page']),
898
+ isset($_POST[self::FB_VISIBILITY]));
899
+ $this->update_product_item($woo_product, $fb_product_item_id);
900
+ return $fb_product_item_id;
901
+ } else {
902
+ // Check if this is a new product item for an existing product group
903
+ if ($woo_product->get_parent_id()) {
904
+ $fb_product_group_id = $this->get_product_fbid(
905
+ self::FB_PRODUCT_GROUP_ID,
906
+ $woo_product->get_parent_id(),
907
+ $woo_product);
908
+
909
+ // New variant added
910
+ if ($fb_product_group_id) {
911
+ return
912
+ $this->create_product_simple($woo_product, $fb_product_group_id);
913
+ } else {
914
+ WC_Facebookcommerce_Utils::fblog(
915
+ "Wrong! simple_product_publish called without group ID for
916
+ a variable product!", array(), true);
917
+ }
918
+ } else {
919
+ return $this->create_product_simple($woo_product); // new product
920
+ }
921
+ }
922
+ }
923
+
924
+ function create_product_variable($woo_product) {
925
+ $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id($woo_product);
926
+
927
+ $fb_product_group_id = $this->create_product_group(
928
+ $woo_product,
929
+ $retailer_id,
930
+ true);
931
+
932
+ if ($fb_product_group_id) {
933
+ $child_products = $woo_product->get_children();
934
+ $variation_id = $woo_product->find_matching_product_variation();
935
+ foreach ($child_products as $item_id) {
936
+ $child_product = new WC_Facebook_Product($item_id, $woo_product);
937
+ $retailer_id =
938
+ WC_Facebookcommerce_Utils::get_fb_retailer_id($child_product);
939
+ $fb_product_item_id = $this->create_product_item(
940
+ $child_product,
941
+ $retailer_id,
942
+ $fb_product_group_id);
943
+ if ($item_id == $variation_id && $fb_product_item_id) {
944
+ $this->set_default_variant($fb_product_group_id, $fb_product_item_id);
945
+ }
946
+ }
947
+ }
948
+ }
949
+
950
+ /**
951
+ * Create product group and product, store fb-specific info
952
+ **/
953
+ function create_product_simple($woo_product, $fb_product_group_id = null) {
954
+ $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id($woo_product);
955
+
956
+ if (!$fb_product_group_id) {
957
+ $fb_product_group_id = $this->create_product_group(
958
+ $woo_product,
959
+ $retailer_id);
960
+ }
961
+
962
+ if ($fb_product_group_id) {
963
+ $fb_product_item_id = $this->create_product_item(
964
+ $woo_product,
965
+ $retailer_id,
966
+ $fb_product_group_id);
967
+ return $fb_product_item_id;
968
+ }
969
+ }
970
+
971
+ function create_product_group($woo_product, $retailer_id, $variants = false) {
972
+
973
+ $product_group_data = array(
974
+ 'retailer_id' => $retailer_id,
975
+ );
976
+
977
+ // Default visibility on create = published
978
+ $woo_product->fb_visibility = true;
979
+ update_post_meta($woo_product->get_id(), self::FB_VISIBILITY, true);
980
+
981
+ if ($variants) {
982
+ $product_group_data['variants'] =
983
+ $woo_product->prepare_variants_for_group();
984
+ }
985
+
986
+ $create_product_group_result = $this->check_api_result(
987
+ $this->fbgraph->create_product_group(
988
+ $this->product_catalog_id,
989
+ $product_group_data),
990
+ $product_group_data,
991
+ $woo_product->get_id());
992
+
993
+ // New variant added
994
+ if ($create_product_group_result) {
995
+ $decode_result = WC_Facebookcommerce_Utils::decode_json($create_product_group_result['body']);
996
+ $fb_product_group_id = $decode_result->id;
997
+ // update_post_meta is actually more of a create_or_update
998
+ update_post_meta(
999
+ $woo_product->get_id(),
1000
+ self::FB_PRODUCT_GROUP_ID,
1001
+ $fb_product_group_id);
1002
+
1003
+ $this->display_success_message(
1004
+ 'Created product group <a href="https://facebook.com/'.
1005
+ $fb_product_group_id . '" target="_blank">' .
1006
+ $fb_product_group_id . '</a> on Facebook.');
1007
+
1008
+ return $fb_product_group_id;
1009
+ }
1010
+ }
1011
+
1012
+ function create_product_item($woo_product, $retailer_id, $product_group_id) {
1013
+ // Default visibility on create = published
1014
+ $woo_product->fb_visibility = true;
1015
+ $product_data = $woo_product->prepare_product($retailer_id);
1016
+ if (!$product_data['price']) {
1017
+ return 0;
1018
+ }
1019
+
1020
+ update_post_meta($woo_product->get_id(), self::FB_VISIBILITY, true);
1021
+
1022
+ $product_result = $this->check_api_result(
1023
+ $this->fbgraph->create_product_item(
1024
+ $product_group_id,
1025
+ $product_data),
1026
+ $product_data,
1027
+ $woo_product->get_id());
1028
+
1029
+ if ($product_result) {
1030
+ $decode_result = WC_Facebookcommerce_Utils::decode_json($product_result['body']);
1031
+ $fb_product_item_id = $decode_result->id;
1032
+
1033
+ update_post_meta($woo_product->get_id(),
1034
+ self::FB_PRODUCT_ITEM_ID, $fb_product_item_id);
1035
+
1036
+ $this->display_success_message(
1037
+ 'Created product item <a href="https://facebook.com/'.
1038
+ $fb_product_item_id . '" target="_blank">' .
1039
+ $fb_product_item_id . '</a> on Facebook.');
1040
+
1041
+ return $fb_product_item_id;
1042
+ }
1043
+ }
1044
+
1045
+
1046
+ /**
1047
+ * Update existing product group (variant data only)
1048
+ **/
1049
+ function update_product_group($woo_product) {
1050
+ $fb_product_group_id = $this->get_product_fbid(
1051
+ self::FB_PRODUCT_GROUP_ID,
1052
+ $woo_product->get_id(),
1053
+ $woo_product);
1054
+
1055
+ if (!$fb_product_group_id) {
1056
+ return;
1057
+ }
1058
+
1059
+ $variants = $woo_product->prepare_variants_for_group();
1060
+
1061
+ if (!$variants) {
1062
+ WC_Facebookcommerce_Utils::log(
1063
+ sprintf(__('Nothing to update for product group for %1$s',
1064
+ 'facebook-for-woocommerce'),
1065
+ $fb_product_group_id));
1066
+ return;
1067
+ }
1068
+
1069
+ $product_group_data = array(
1070
+ 'variants' => $variants
1071
+ );
1072
+
1073
+ $result = $this->check_api_result(
1074
+ $this->fbgraph->update_product_group(
1075
+ $fb_product_group_id,
1076
+ $product_group_data));
1077
+
1078
+ if ($result) {
1079
+ $this->display_success_message(
1080
+ 'Updated product group <a href="https://facebook.com/'.
1081
+ $fb_product_group_id .'" target="_blank">' . $fb_product_group_id .
1082
+ '</a> on Facebook.');
1083
+ }
1084
+ }
1085
+
1086
+ /**
1087
+ * Update existing product
1088
+ **/
1089
+ function update_product_item($woo_product, $fb_product_item_id) {
1090
+ $product_data = $woo_product->prepare_product();
1091
+
1092
+ $result = $this->check_api_result(
1093
+ $this->fbgraph->update_product_item(
1094
+ $fb_product_item_id,
1095
+ $product_data));
1096
+
1097
+ if ($result) {
1098
+ $this->display_success_message(
1099
+ 'Updated product <a href="https://facebook.com/'. $fb_product_item_id .
1100
+ '" target="_blank">' . $fb_product_item_id . '</a> on Facebook.');
1101
+ }
1102
+ }
1103
+
1104
+ /**
1105
+ * Save settings via AJAX (to preserve window context for onboarding)
1106
+ **/
1107
+ function ajax_save_fb_settings() {
1108
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('save settings', true);
1109
+
1110
+ if (isset($_REQUEST)) {
1111
+ if (!isset($_REQUEST['facebook_for_woocommerce'])) {
1112
+ // This is not a request from our plugin,
1113
+ // some other handler or plugin probably
1114
+ // wants to handle it and wp_die() after.
1115
+ return;
1116
+ }
1117
+
1118
+ if (isset($_REQUEST['api_key']) && ctype_alnum($_REQUEST['api_key'])) {
1119
+ $this->settings['fb_api_key'] = $_REQUEST['api_key'];
1120
+ }
1121
+ if (isset($_REQUEST['product_catalog_id']) &&
1122
+ ctype_digit($_REQUEST['product_catalog_id'])) {
1123
+
1124
+ if ($this->product_catalog_id != '' &&
1125
+ $this->product_catalog_id != $_REQUEST['product_catalog_id']) {
1126
+ $this->reset_all_products();
1127
+ }
1128
+ $this->settings['fb_product_catalog_id'] =
1129
+ $_REQUEST['product_catalog_id'];
1130
+ }
1131
+ if (isset($_REQUEST['pixel_id']) && ctype_digit($_REQUEST['pixel_id'])) {
1132
+ // To prevent race conditions with pixel-only settings,
1133
+ // only save a pixel if we already have an API key.
1134
+ if ($this->settings['fb_api_key']) {
1135
+ $this->settings['fb_pixel_id'] = $_REQUEST['pixel_id'];
1136
+ if ($this->pixel_id != $_REQUEST['pixel_id']) {
1137
+ $this->settings['pixel_install_time'] = current_time('mysql');
1138
+ }
1139
+ } else {
1140
+ WC_Facebookcommerce_Utils::log(
1141
+ "Got pixel-only settings, doing nothing");
1142
+ echo "Not saving pixel-only settings";
1143
+ wp_die();
1144
+ }
1145
+ }
1146
+ if (isset($_REQUEST['pixel_use_pii'])) {
1147
+ $this->settings['fb_pixel_use_pii'] =
1148
+ ($_REQUEST['pixel_use_pii'] === 'true' ||
1149
+ $_REQUEST['pixel_use_pii'] === true) ? 'yes' : 'no';
1150
+ }
1151
+ if (isset($_REQUEST['page_id']) &&
1152
+ ctype_digit($_REQUEST['page_id'])) {
1153
+ $this->settings['fb_page_id'] = $_REQUEST['page_id'];
1154
+ }
1155
+ if (isset($_REQUEST['external_merchant_settings_id']) &&
1156
+ ctype_digit($_REQUEST['external_merchant_settings_id'])) {
1157
+ $this->settings['fb_external_merchant_settings_id'] =
1158
+ $_REQUEST['external_merchant_settings_id'];
1159
+ }
1160
+ if (isset($_REQUEST['is_messenger_chat_plugin_enabled'])) {
1161
+ $this->settings['is_messenger_chat_plugin_enabled'] =
1162
+ ($_REQUEST['is_messenger_chat_plugin_enabled'] === 'true' ||
1163
+ $_REQUEST['is_messenger_chat_plugin_enabled'] === true) ? 'yes' : 'no';
1164
+ }
1165
+ if (isset($_REQUEST['facebook_jssdk_version'])) {
1166
+ $this->settings['facebook_jssdk_version'] =
1167
+ sanitize_text_field($_REQUEST['facebook_jssdk_version']);
1168
+ }
1169
+ if (isset($_REQUEST['msger_chat_customization_greeting_text_code'])
1170
+ && ctype_digit($_REQUEST['msger_chat_customization_greeting_text_code'])) {
1171
+ $this->settings['msger_chat_customization_greeting_text_code'] =
1172
+ $_REQUEST['msger_chat_customization_greeting_text_code'];
1173
+ }
1174
+ if (isset($_REQUEST['msger_chat_customization_locale'])) {
1175
+ $this->settings['msger_chat_customization_locale'] =
1176
+ sanitize_text_field($_REQUEST['msger_chat_customization_locale']);
1177
+ }
1178
+ if (isset($_REQUEST['msger_chat_customization_theme_color_code']) &&
1179
+ ctype_digit($_REQUEST['msger_chat_customization_theme_color_code'])) {
1180
+ $this->settings['msger_chat_customization_theme_color_code'] =
1181
+ $_REQUEST['msger_chat_customization_theme_color_code'];
1182
+ }
1183
+
1184
+ update_option(
1185
+ $this->get_option_key(),
1186
+ apply_filters(
1187
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1188
+ $this->settings));
1189
+
1190
+ WC_Facebookcommerce_Utils::log("Settings saved!");
1191
+ echo "settings_saved";
1192
+ } else {
1193
+ echo "No Request";
1194
+ }
1195
+
1196
+ wp_die();
1197
+ }
1198
+
1199
+ /**
1200
+ * Delete all settings via AJAX
1201
+ **/
1202
+ function ajax_delete_fb_settings() {
1203
+ if (!WC_Facebookcommerce_Utils::check_woo_ajax_permissions('delete settings', false)) {
1204
+ return;
1205
+ }
1206
+
1207
+ // Do not allow reset in the middle of product sync
1208
+ $currently_syncing = get_transient(self::FB_SYNC_IN_PROGRESS);
1209
+ if ($currently_syncing) {
1210
+ wp_send_json('A Facebook product sync is currently in progress.
1211
+ Deleting settings during product sync may cause errors.');
1212
+ return;
1213
+ }
1214
+
1215
+ if (isset($_REQUEST)) {
1216
+ $ems = $this->settings['fb_external_merchant_settings_id'];
1217
+ if ($ems) {
1218
+ WC_Facebookcommerce_Utils::fblog(
1219
+ "Deleted all settings!",
1220
+ array(),
1221
+ false,
1222
+ $ems);
1223
+ }
1224
+
1225
+ $this->init_settings();
1226
+ $this->settings['fb_api_key'] = '';
1227
+ $this->settings['fb_product_catalog_id'] = '';
1228
+
1229
+ $this->settings['fb_pixel_id'] = '';
1230
+ $this->settings['fb_pixel_use_pii'] = 'no';
1231
+
1232
+ $this->settings['fb_page_id'] = '';
1233
+ $this->settings['fb_external_merchant_settings_id'] = '';
1234
+ $this->settings['pixel_install_time'] = '';
1235
+ $this->settings['fb_feed_id'] = '';
1236
+ $this->settings['fb_upload_id'] = '';
1237
+ $this->settings['upload_end_time'] = '';
1238
+
1239
+ WC_Facebookcommerce_Pixel::set_pixel_id(0);
1240
+
1241
+ update_option(
1242
+ $this->get_option_key(),
1243
+ apply_filters(
1244
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1245
+ $this->settings));
1246
+
1247
+ // Clean up old messages
1248
+ delete_transient('facebook_plugin_api_error');
1249
+ delete_transient('facebook_plugin_api_success');
1250
+ delete_transient('facebook_plugin_api_warning');
1251
+ delete_transient('facebook_plugin_api_info');
1252
+ delete_transient('facebook_plugin_api_sticky');
1253
+
1254
+ $this->reset_all_products();
1255
+
1256
+ WC_Facebookcommerce_Utils::log("Settings deleted");
1257
+ echo "Settings Deleted";
1258
+
1259
+ }
1260
+
1261
+ wp_die();
1262
+ }
1263
+
1264
+ /**
1265
+ * Check Feed Upload Status
1266
+ **/
1267
+ function ajax_check_feed_upload_status() {
1268
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('check feed upload status', true);
1269
+ if ($this->settings['fb_api_key']) {
1270
+ $response = array(
1271
+ 'connected' => true,
1272
+ 'status' => 'in progress',
1273
+ );
1274
+ if ($this->settings['fb_upload_id']) {
1275
+ if (!isset($this->fbproductfeed)) {
1276
+ if (!class_exists('WC_Facebook_Product_Feed')) {
1277
+ include_once 'includes/fbproductfeed.php';
1278
+ }
1279
+ $this->fbproductfeed = new WC_Facebook_Product_Feed(
1280
+ $this->product_catalog_id, $this->fbgraph);
1281
+ }
1282
+ $status = $this->fbproductfeed->is_upload_complete($this->settings);
1283
+
1284
+ $response['status'] = $status;
1285
+ } else {
1286
+ $response = array(
1287
+ 'connected' => true,
1288
+ 'status' => 'error',
1289
+ );
1290
+ }
1291
+ if ($response['status'] == 'complete') {
1292
+ update_option(
1293
+ $this->get_option_key(),
1294
+ apply_filters(
1295
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
1296
+ $this->settings));
1297
+ }
1298
+ } else {
1299
+ $response = array(
1300
+ 'connected' => false,
1301
+ );
1302
+ }
1303
+ printf(json_encode($response));
1304
+ wp_die();
1305
+ }
1306
+
1307
+ /**
1308
+ * Display custom success message (sugar)
1309
+ **/
1310
+ function display_success_message($msg) {
1311
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1312
+ set_transient('facebook_plugin_api_success', $msg,
1313
+ self::FB_MESSAGE_DISPLAY_TIME);
1314
+ }
1315
+
1316
+ /**
1317
+ * Display custom warning message (sugar)
1318
+ **/
1319
+ function display_warning_message($msg) {
1320
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1321
+ set_transient('facebook_plugin_api_warning', $msg,
1322
+ self::FB_MESSAGE_DISPLAY_TIME);
1323
+ }
1324
+
1325
+ /**
1326
+ * Display custom info message (sugar)
1327
+ **/
1328
+ function display_info_message($msg) {
1329
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1330
+ set_transient('facebook_plugin_api_info', $msg,
1331
+ self::FB_MESSAGE_DISPLAY_TIME);
1332
+ }
1333
+
1334
+ /**
1335
+ * Display custom "sticky" info message.
1336
+ * Call remove_sticky_message or wait for time out.
1337
+ **/
1338
+ function display_sticky_message($msg) {
1339
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1340
+ set_transient('facebook_plugin_api_sticky', $msg,
1341
+ self::FB_MESSAGE_DISPLAY_TIME);
1342
+ }
1343
+
1344
+ /**
1345
+ * Remove custom "sticky" info message
1346
+ **/
1347
+ function remove_sticky_message() {
1348
+ delete_transient('facebook_plugin_api_sticky');
1349
+ }
1350
+
1351
+ function remove_resync_message() {
1352
+ $msg = get_transient('facebook_plugin_api_sticky');
1353
+ if ($msg && strpos($msg, 'Sync') !== false) {
1354
+ delete_transient('facebook_plugin_resync_sticky');
1355
+ }
1356
+ }
1357
+
1358
+ /**
1359
+ * Display custom error message (sugar)
1360
+ **/
1361
+ function display_error_message($msg) {
1362
+ $msg = self::FB_ADMIN_MESSAGE_PREPEND . $msg;
1363
+ WC_Facebookcommerce_Utils::log($msg);
1364
+ set_transient('facebook_plugin_api_error', $msg,
1365
+ self::FB_MESSAGE_DISPLAY_TIME);
1366
+ }
1367
+
1368
+ /**
1369
+ * Display error message from API result (sugar)
1370
+ **/
1371
+ function display_error_message_from_result($result) {
1372
+ $msg = json_decode($result['body'])->error->message;
1373
+ $this->display_error_message($msg);
1374
+ }
1375
+
1376
+ /**
1377
+ * Deal with FB API responses, display error if FB API returns error
1378
+ *
1379
+ * @return result if response is 200, null otherwise
1380
+ **/
1381
+ function check_api_result($result, $logdata = null, $wpid = null) {
1382
+ if (is_wp_error($result)) {
1383
+ WC_Facebookcommerce_Utils::log($result->get_error_message());
1384
+ $this->display_error_message(
1385
+ "There was an issue connecting to the Facebook API: ".
1386
+ $result->get_error_message());
1387
+ return;
1388
+ }
1389
+ if ($result['response']['code'] != '200') {
1390
+ // Catch 10800 fb error code ("Duplicate retailer ID") and capture FBID
1391
+ // if possible, otherwise let user know we found dupe SKUs
1392
+ $body = WC_Facebookcommerce_Utils::decode_json($result['body']);
1393
+ if ($body && $body->error->code == '10800') {
1394
+ $error_data = $body->error->error_data; // error_data may contain FBIDs
1395
+ if ($error_data && $wpid) {
1396
+ $existing_id = $this->get_existing_fbid($error_data, $wpid);
1397
+ if ($existing_id) {
1398
+ // Add "existing_id" ID to result
1399
+ $body->id = $existing_id;
1400
+ $result['body'] = json_encode($body);
1401
+ return $result;
1402
+ }
1403
+ }
1404
+ } else {
1405
+ $this->display_error_message_from_result($result);
1406
+ }
1407
+
1408
+ WC_Facebookcommerce_Utils::log($result);
1409
+ $data = array(
1410
+ 'result' => $result,
1411
+ 'data' => $logdata,
1412
+ );
1413
+ WC_Facebookcommerce_Utils::fblog(
1414
+ 'Non-200 error code from FB',
1415
+ $data,
1416
+ true);
1417
+ return null;
1418
+ }
1419
+ return $result;
1420
+ }
1421
+
1422
+ function ajax_woo_adv_bulk_edit_compat($import_id) {
1423
+ if (!WC_Facebookcommerce_Utils::check_woo_ajax_permissions('adv bulk edit', false)) {
1424
+ return;
1425
+ }
1426
+ $type = isset($_POST["type"]) ? $_POST["type"] : "";
1427
+ if (strpos($type, 'product') !== false && strpos($type, 'load') === false) {
1428
+ $this->display_out_of_sync_message("advanced bulk edit");
1429
+ }
1430
+ }
1431
+
1432
+ function wp_all_import_compat($import_id) {
1433
+ $import = new PMXI_Import_Record();
1434
+ $import->getById($import_id);
1435
+ if (!$import->isEmpty() && in_array($import->options['custom_type'], array('product', 'product_variation'))) {
1436
+ $this->display_out_of_sync_message("import");
1437
+ }
1438
+ }
1439
+
1440
+ function display_out_of_sync_message($action_name) {
1441
+ $this->display_sticky_message(
1442
+ sprintf(
1443
+ 'Products may be out of Sync with Facebook due to your recent '.$action_name.'.'.
1444
+ ' <a href="%s&fb_force_resync=true&remove_sticky=true">Re-Sync them with FB.</a>',
1445
+ WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL));
1446
+ }
1447
+
1448
+ /**
1449
+ * If we get a product group ID or product item ID back for a dupe retailer
1450
+ * id error, update existing ID.
1451
+ *
1452
+ * @return null
1453
+ **/
1454
+ function get_existing_fbid($error_data, $wpid) {
1455
+ if (isset($error_data->product_group_id)) {
1456
+ update_post_meta(
1457
+ $wpid,
1458
+ self::FB_PRODUCT_GROUP_ID,
1459
+ (string)$error_data->product_group_id);
1460
+ return $error_data->product_group_id;
1461
+ }
1462
+ else if (isset($error_data->product_item_id)) {
1463
+ update_post_meta(
1464
+ $wpid,
1465
+ self::FB_PRODUCT_ITEM_ID,
1466
+ (string)$error_data->product_item_id);
1467
+ return $error_data->product_item_id;
1468
+ } else {
1469
+ return;
1470
+ }
1471
+ }
1472
+
1473
+ /**
1474
+ * Check for api key and any other API errors
1475
+ **/
1476
+ function checks() {
1477
+ // Check required fields
1478
+
1479
+ if (!$this->api_key || !$this->product_catalog_id) {
1480
+ echo $this->get_message_html(sprintf(__('%1$sFacebook for WooCommerce
1481
+ is almost ready.%2$s To complete your configuration, %3$scomplete the
1482
+ setup steps%4$s.',
1483
+ 'facebook-for-woocommerce'), '<strong>', '</strong>',
1484
+
1485
+ '<a href="' . esc_url(WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL) . '">',
1486
+ '</a>'), 'info');
1487
+ }
1488
+
1489
+ // WooCommerce 2.x upgrade nag
1490
+ if ($this->api_key && (!isset($this->background_processor))) {
1491
+ echo $this->get_message_html(sprintf(__(
1492
+ 'Facebook product sync may not work correctly in WooCommerce version
1493
+ %1$s. Please upgrade to WooCommerce 3.',
1494
+ 'facebook-for-woocommerce'), WC()->version), 'warning');
1495
+ }
1496
+
1497
+ $this->maybe_display_facebook_api_messages();
1498
+ }
1499
+
1500
+ function get_sample_product_feed() {
1501
+ ob_start();
1502
+
1503
+ // Get up to 12 published posts that are products
1504
+ $args = array(
1505
+ 'post_type' => 'product',
1506
+ 'post_status' => 'publish',
1507
+ 'posts_per_page' => 12,
1508
+ 'fields' => 'ids'
1509
+ );
1510
+
1511
+ $post_ids = get_posts($args);
1512
+ $items = array();
1513
+
1514
+ foreach ($post_ids as $post_id) {
1515
+
1516
+ $woo_product = new WC_Facebook_Product($post_id);
1517
+ $product_data = $woo_product->prepare_product();
1518
+
1519
+ $feed_item = array(
1520
+ 'title' => strip_tags($product_data['name']),
1521
+ 'availability' => $woo_product->is_in_stock() ? 'in stock' :
1522
+ 'out of stock',
1523
+ 'description' => strip_tags($product_data['description']),
1524
+ 'id' => $product_data['retailer_id'],
1525
+ 'image_link' => $product_data['image_url'],
1526
+ 'brand' => strip_tags(WC_Facebookcommerce_Utils::get_store_name()),
1527
+ 'link' => $product_data['url'],
1528
+ 'price' => $product_data['price'] . ' ' . get_woocommerce_currency(),
1529
+ );
1530
+
1531
+ array_push($items, $feed_item);
1532
+ }
1533
+ // https://codex.wordpress.org/Function_Reference/wp_reset_postdata
1534
+ wp_reset_postdata();
1535
+ ob_end_clean();
1536
+ return json_encode(array($items));
1537
+ }
1538
+
1539
+ /**
1540
+ * Loop through array of WPIDs to remove metadata.
1541
+ **/
1542
+ function delete_post_meta_loop($products) {
1543
+ foreach ($products as $product_id) {
1544
+ delete_post_meta($product_id, self::FB_PRODUCT_GROUP_ID);
1545
+ delete_post_meta($product_id, self::FB_PRODUCT_ITEM_ID);
1546
+ delete_post_meta($product_id, self::FB_VISIBILITY);
1547
+ }
1548
+ }
1549
+
1550
+ /**
1551
+ * Remove FBIDs from all products when resetting store.
1552
+ **/
1553
+ function reset_all_products() {
1554
+ if (!is_admin()) {
1555
+ WC_Facebookcommerce_Utils::log("Not resetting any FBIDs from products,
1556
+ must call reset from admin context.");
1557
+ return false;
1558
+ }
1559
+
1560
+ $test_instance = WC_Facebook_Integration_Test::get_instance($this);
1561
+ $this->test_mode = $test_instance::$test_mode;
1562
+
1563
+ // Include draft products (omit 'post_status' => 'publish')
1564
+ WC_Facebookcommerce_Utils::log("Removing FBIDs from all products");
1565
+
1566
+ $post_ids = get_posts(array(
1567
+ 'post_type' => 'product',
1568
+ 'posts_per_page' => -1,
1569
+ 'fields' => 'ids'
1570
+ ));
1571
+
1572
+ $children = array();
1573
+ foreach ($post_ids as $post_id) {
1574
+ $children = array_merge(get_posts(array(
1575
+ 'post_type' => 'product_variation',
1576
+ 'posts_per_page' => -1,
1577
+ 'post_parent' => $post_id,
1578
+ 'fields' => 'ids'
1579
+ )), $children);
1580
+ }
1581
+ $post_ids = array_merge($post_ids, $children);
1582
+ $this->delete_post_meta_loop($post_ids);
1583
+
1584
+ WC_Facebookcommerce_Utils::log("Product FBIDs deleted");
1585
+ return true;
1586
+ }
1587
+
1588
+ /**
1589
+ * Remove FBIDs from a single WC product
1590
+ **/
1591
+ function reset_single_product($wp_id) {
1592
+ $woo_product = new WC_Facebook_Product($wp_id);
1593
+ $products = array($woo_product->get_id());
1594
+ if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
1595
+ $products = array_merge($products, $woo_product->get_children());
1596
+ }
1597
+
1598
+ $this->delete_post_meta_loop($products);
1599
+
1600
+ WC_Facebookcommerce_Utils::log("Deleted FB Metadata for product " . $wp_id);
1601
+ }
1602
+
1603
+ function ajax_reset_all_fb_products() {
1604
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('reset products', true);
1605
+ $this->reset_all_products();
1606
+ wp_reset_postdata();
1607
+ wp_die();
1608
+ }
1609
+
1610
+ function ajax_reset_single_fb_product() {
1611
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('reset single product', true);
1612
+ if (!isset($_POST['wp_id'])) {
1613
+ wp_die();
1614
+ }
1615
+
1616
+ $wp_id = sanitize_text_field($_POST['wp_id']);
1617
+ $woo_product = new WC_Facebook_Product($wp_id);
1618
+ if ($woo_product) {
1619
+ $this->reset_single_product($wp_id);
1620
+ }
1621
+
1622
+ wp_reset_postdata();
1623
+ wp_die();
1624
+ }
1625
+
1626
+ function ajax_delete_fb_product() {
1627
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('delete single product', true);
1628
+ if (!isset($_POST['wp_id'])) {
1629
+ wp_die();
1630
+ }
1631
+
1632
+ $wp_id = sanitize_text_field($_POST['wp_id']);
1633
+ $this->on_product_delete($wp_id);
1634
+ $this->reset_single_product($wp_id);
1635
+ wp_reset_postdata();
1636
+ wp_die();
1637
+ }
1638
+
1639
+ /**
1640
+ * Special function to run all visible products through on_product_publish
1641
+ **/
1642
+ function ajax_sync_all_fb_products() {
1643
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('syncall products', true);
1644
+ if (get_option('fb_disable_sync_on_dev_environment', false)) {
1645
+ WC_Facebookcommerce_Utils::log(
1646
+ 'Sync to FB Page is not allowed in Dev Environment');
1647
+ wp_die();
1648
+ return;
1649
+ }
1650
+
1651
+ if (!$this->api_key || !$this->product_catalog_id) {
1652
+ WC_Facebookcommerce_Utils::log("No API key or catalog ID: " .
1653
+ $this->api_key . ' and ' . $this->product_catalog_id);
1654
+ wp_die();
1655
+ return;
1656
+ }
1657
+ $this->remove_resync_message();
1658
+
1659
+ $currently_syncing = get_transient(self::FB_SYNC_IN_PROGRESS);
1660
+
1661
+ if (isset($this->background_processor)) {
1662
+ if ($this->background_processor->is_updating()) {
1663
+ $this->background_processor->handle_cron_healthcheck();
1664
+ $currently_syncing = 1;
1665
+ }
1666
+ }
1667
+
1668
+ if ($currently_syncing) {
1669
+ WC_Facebookcommerce_Utils::log('Not syncing, sync in progress');
1670
+ WC_Facebookcommerce_Utils::fblog(
1671
+ 'Tried to sync during an in-progress sync!', array(), true);
1672
+ $this->display_warning_message('A product sync is in progress.
1673
+ Please wait until the sync finishes before starting a new one.');
1674
+ wp_die();
1675
+ return;
1676
+ }
1677
+
1678
+ $is_valid_product_catalog =
1679
+ $this->fbgraph->validate_product_catalog($this->product_catalog_id);
1680
+
1681
+ if (!$is_valid_product_catalog) {
1682
+ WC_Facebookcommerce_Utils::log('Not syncing, invalid product catalog!');
1683
+ WC_Facebookcommerce_Utils::fblog(
1684
+ 'Tried to sync with an invalid product catalog!', array(), true);
1685
+ $this->display_warning_message('We\'ve detected that your
1686
+ Facebook Product Catalog is no longer valid. This may happen if it was
1687
+ deleted, or this may be a transient error.
1688
+ If this error persists please delete your settings via
1689
+ "Re-configure Facebook Settings > Advanced Settings > Delete Settings"
1690
+ and try setup again');
1691
+ wp_die();
1692
+ return;
1693
+ }
1694
+
1695
+ // Cache the cart URL to display a warning in case it changes later
1696
+ $cart_url = get_option(self::FB_CART_URL);
1697
+ if ($cart_url != wc_get_cart_url()) {
1698
+ update_option(self::FB_CART_URL, wc_get_cart_url());
1699
+ }
1700
+
1701
+ $sanitized_settings = $this->settings;
1702
+ unset($sanitized_settings['fb_api_key']);
1703
+
1704
+ // Get all published posts. First unsynced then already-synced.
1705
+ $post_ids_new = WC_Facebookcommerce_Utils::get_wp_posts(
1706
+ self::FB_PRODUCT_GROUP_ID, 'NOT EXISTS');
1707
+ $post_ids_old = WC_Facebookcommerce_Utils::get_wp_posts(
1708
+ self::FB_PRODUCT_GROUP_ID, 'EXISTS');
1709
+
1710
+ $total_new = count($post_ids_new);
1711
+ $total_old = count($post_ids_old);
1712
+ $post_ids = array_merge($post_ids_new, $post_ids_old);
1713
+ $total = count($post_ids);
1714
+
1715
+ WC_Facebookcommerce_Utils::fblog(
1716
+ 'Attempting to sync ' . $total . ' ( ' .
1717
+ $total_new . ' new) products with settings: ',
1718
+ $sanitized_settings,
1719
+ false);
1720
+
1721
+ // Check for background processing (Woo 3.x.x)
1722
+ if (isset($this->background_processor)) {
1723
+ $starting_message = sprintf(
1724
+ 'Starting background sync to Facebook: %d products...',
1725
+ $total);
1726
+
1727
+ set_transient(
1728
+ self::FB_SYNC_IN_PROGRESS,
1729
+ true,
1730
+ self::FB_SYNC_TIMEOUT);
1731
+
1732
+ set_transient(
1733
+ self::FB_SYNC_REMAINING,
1734
+ (int)$total);
1735
+
1736
+ $this->display_info_message($starting_message);
1737
+ WC_Facebookcommerce_Utils::log($starting_message);
1738
+
1739
+ foreach ($post_ids as $post_id) {
1740
+ WC_Facebookcommerce_Utils::log("Pushing post to queue: " . $post_id);
1741
+ $this->background_processor->push_to_queue($post_id);
1742
+ }
1743
+
1744
+ $this->background_processor->save()->dispatch();
1745
+ // reset FB_SYNC_REMAINING to avoid race condition
1746
+ set_transient(
1747
+ self::FB_SYNC_REMAINING,
1748
+ (int)$total);
1749
+ // handle_cron_healthcheck must be called
1750
+ // https://github.com/A5hleyRich/wp-background-processing/issues/34
1751
+ $this->background_processor->handle_cron_healthcheck();
1752
+ } else {
1753
+ // Oldschool sync for WooCommerce 2.x
1754
+ $count = ($total_old === $total) ? 0 : $total_old;
1755
+ foreach ($post_ids as $post_id) {
1756
+ // Repeatedly overwrite sync total while in actual sync loop
1757
+ set_transient(
1758
+ self::FB_SYNC_IN_PROGRESS,
1759
+ true,
1760
+ self::FB_SYNC_TIMEOUT);
1761
+
1762
+ $this->display_sticky_message(
1763
+ sprintf(
1764
+ 'Syncing products to Facebook: %d out of %d...',
1765
+ // Display different # when resuming to avoid confusion.
1766
+ min($count, $total),
1767
+ $total),
1768
+ true);
1769
+
1770
+ $this->on_product_publish($post_id);
1771
+ $count++;
1772
+ }
1773
+ WC_Facebookcommerce_Utils::log('Synced ' . $count . ' products');
1774
+ $this->remove_sticky_message();
1775
+ $this->display_info_message('Facebook product sync complete!');
1776
+ delete_transient(self::FB_SYNC_IN_PROGRESS);
1777
+ WC_Facebookcommerce_Utils::fblog(
1778
+ 'Product sync complete. Total products synced: ' . $count);
1779
+ }
1780
+
1781
+ // https://codex.wordpress.org/Function_Reference/wp_reset_postdata
1782
+ wp_reset_postdata();
1783
+
1784
+ // This is important, for some reason.
1785
+ // See https://codex.wordpress.org/AJAX_in_Plugins
1786
+ wp_die();
1787
+ }
1788
+
1789
+ /**
1790
+ * Special function to run all visible products by uploading feed.
1791
+ **/
1792
+ function ajax_sync_all_fb_products_using_feed() {
1793
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
1794
+ 'syncall products using feed', !$this->test_mode);
1795
+ return $this->sync_all_fb_products_using_feed();
1796
+ }
1797
+
1798
+ // Separate entry point that bypasses permission check for use in cron.
1799
+ function sync_all_fb_products_using_feed() {
1800
+ if (get_option('fb_disable_sync_on_dev_environment', false)) {
1801
+ WC_Facebookcommerce_Utils::log(
1802
+ 'Sync to FB Page is not allowed in Dev Environment');
1803
+ $this->fb_wp_die();
1804
+ return false;
1805
+ }
1806
+
1807
+ if (!$this->api_key || !$this->product_catalog_id) {
1808
+ self::log("No API key or catalog ID: " . $this->api_key .
1809
+ ' and ' . $this->product_catalog_id);
1810
+ $this->fb_wp_die();
1811
+ return false;
1812
+ }
1813
+ $this->remove_resync_message();
1814
+ $is_valid_product_catalog =
1815
+ $this->fbgraph->validate_product_catalog($this->product_catalog_id);
1816
+
1817
+ if (!$is_valid_product_catalog) {
1818
+ WC_Facebookcommerce_Utils::log('Not syncing, invalid product catalog!');
1819
+ WC_Facebookcommerce_Utils::fblog(
1820
+ 'Tried to sync with an invalid product catalog!', array(), true);
1821
+ $this->display_warning_message('We\'ve detected that your
1822
+ Facebook Product Catalog is no longer valid. This may happen if it was
1823
+ deleted, or this may be a transient error.
1824
+ If this error persists please delete your settings via
1825
+ "Re-configure Facebook Settings > Advanced Settings > Delete Settings"
1826
+ and try setup again');
1827
+ $this->fb_wp_die();
1828
+ return false;
1829
+ }
1830
+
1831
+ // Cache the cart URL to display a warning in case it changes later
1832
+ $cart_url = get_option(self::FB_CART_URL);
1833
+ if ($cart_url != wc_get_cart_url()) {
1834
+ update_option(self::FB_CART_URL, wc_get_cart_url());
1835
+ }
1836
+
1837
+ if (!class_exists('WC_Facebook_Product_Feed')) {
1838
+ include_once 'includes/fbproductfeed.php';
1839
+ }
1840
+ if ($this->test_mode) {
1841
+ $this->fbproductfeed = new WC_Facebook_Product_Feed_Test_Mock(
1842
+ $this->product_catalog_id, $this->fbgraph, $this->feed_id);
1843
+ } else {
1844
+ $this->fbproductfeed = new WC_Facebook_Product_Feed(
1845
+ $this->product_catalog_id, $this->fbgraph, $this->feed_id);
1846
+ }
1847
+
1848
+ $upload_success = $this->fbproductfeed->sync_all_products_using_feed();
1849
+ if ($upload_success) {
1850
+ $this->settings['fb_feed_id'] = $this->fbproductfeed->feed_id;
1851
+ $this->settings['fb_upload_id'] = $this->fbproductfeed->upload_id;
1852
+ update_option($this->get_option_key(),
1853
+ apply_filters('woocommerce_settings_api_sanitized_fields_' .
1854
+ $this->id, $this->settings));
1855
+ wp_reset_postdata();
1856
+ $this->fb_wp_die();
1857
+ return true;
1858
+ } else if (!$this->test_mode) {
1859
+ // curl failed, roll back to original sync approach.
1860
+ WC_Facebookcommerce_Utils::fblog(
1861
+ 'Sync all products using feed, curl failed', array(), true);
1862
+ $this->sync_all_products();
1863
+ }
1864
+ return false;
1865
+ }
1866
+
1867
+ /**
1868
+ * Toggles product visibility via AJAX (checks current viz and flips it)
1869
+ **/
1870
+ function ajax_toggle_visibility() {
1871
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('toggle visibility', true);
1872
+ if (!isset($_POST['wp_id']) || ! isset($_POST['published'])) {
1873
+ wp_die();
1874
+ }
1875
+
1876
+ $wp_id = sanitize_text_field($_POST['wp_id']);
1877
+ $published = ($_POST['published']) === 'true' ? true : false;
1878
+
1879
+ $woo_product = new WC_Facebook_Product($wp_id);
1880
+ $products = WC_Facebookcommerce_Utils::get_product_array($woo_product);
1881
+
1882
+ // Loop through product items and flip visibility
1883
+ foreach ($products as $item_id) {
1884
+ $fb_product_item_id = $this->get_product_fbid(
1885
+ self::FB_PRODUCT_ITEM_ID,
1886
+ $item_id);
1887
+ $data = array(
1888
+ 'visibility' => $published ? 'published' : 'staging'
1889
+ );
1890
+
1891
+ $result = $this->check_api_result(
1892
+ $this->fbgraph->update_product_item(
1893
+ $fb_product_item_id,
1894
+ $data));
1895
+
1896
+ if ($result) {
1897
+ update_post_meta($item_id, self::FB_VISIBILITY, $published);
1898
+ update_post_meta($wp_id, self::FB_VISIBILITY, $published);
1899
+ }
1900
+ }
1901
+ wp_die();
1902
+ }
1903
+
1904
+ /**
1905
+ * Initialize Settings Form Fields
1906
+ *
1907
+ * @access public
1908
+ * @return void
1909
+ */
1910
+ function init_form_fields() {
1911
+ $this->form_fields = array(
1912
+ 'fb_settings_heading' => array(
1913
+ 'title' => __('Debug Mode', 'facebook-for-woocommerce'),
1914
+ 'type' => 'title',
1915
+ 'description' => '',
1916
+ 'default' => ''
1917
+ ),
1918
+ 'fb_page_id' => array(
1919
+ 'title' => __('Facebook Page ID', 'facebook-for-woocommerce'),
1920
+ 'type' => 'text',
1921
+ 'description' => __('The unique identifier for your Facebook page.',
1922
+ 'facebook-for-woocommerce'),
1923
+ 'default' => '',
1924
+ ),
1925
+ 'fb_product_catalog_id' => array(
1926
+ 'title' => __('Product Catalog ID', 'facebook-for-woocommerce'),
1927
+ 'type' => 'text',
1928
+ 'description' => __('The unique identifier for your product catalog,
1929
+ on Facebook.', 'facebook-for-woocommerce'),
1930
+ 'default' => ''
1931
+ ),
1932
+ 'fb_pixel_id' => array(
1933
+ 'title' => __('Pixel ID', 'facebook-for-woocommerce'),
1934
+ 'type' => 'text',
1935
+ 'description' => __('The unique identifier for your Facebook pixel',
1936
+ 'facebook-for-woocommerce'),
1937
+ 'default' => ''
1938
+ ),
1939
+ 'fb_pixel_use_pii' => array(
1940
+ 'title' => __('Use Advanced Matching on pixel?',
1941
+ 'facebook-for-woocommerce'),
1942
+ 'type' => 'checkbox',
1943
+ 'description' => __('Enabling Advanced Matching
1944
+ improves audience building.', 'facebook-for-woocommerce'),
1945
+ 'default' => 'yes'
1946
+ ),
1947
+ 'fb_external_merchant_settings_id' => array(
1948
+ 'title' => __('External Merchant Settings ID',
1949
+ 'facebook-for-woocommerce'),
1950
+ 'type' => 'text',
1951
+ 'description' => __('The unique identifier for your external merchant
1952
+ settings, on Facebook.', 'facebook-for-woocommerce'),
1953
+ 'default' => ''
1954
+ ),
1955
+ 'fb_api_key' => array(
1956
+ 'title' => __('API Key', 'facebook-for-woocommerce'),
1957
+ 'type' => 'text',
1958
+ 'description' => sprintf(__('A non-expiring Page Token with
1959
+ %1$smanage_pages%2$s permissions.', 'facebook-for-woocommerce'),
1960
+ '<code>', '</code>'),
1961
+ 'default' => ''
1962
+ ),
1963
+ );
1964
+
1965
+ if (!class_exists('WC_Facebookcommerce_EventsTracker')) {
1966
+ include_once 'includes/fbutils.php';
1967
+ }
1968
+ } // End init_form_fields()
1969
+
1970
+
1971
+ /**
1972
+ * Get message
1973
+ * @return string Error
1974
+ */
1975
+ private function get_message_html($message, $type = 'error') {
1976
+ ob_start();
1977
+
1978
+ ?>
1979
+ <div class="notice is-dismissible notice-<?php echo $type ?>">
1980
+ <p><?php echo $message ?></p>
1981
+ </div>
1982
+ <?php
1983
+ return ob_get_clean();
1984
+ }
1985
+
1986
+ /**
1987
+ * Display relevant messages to user from transients, clear once displayed
1988
+ *
1989
+ * @param void
1990
+ */
1991
+ public function maybe_display_facebook_api_messages() {
1992
+ $error_msg = get_transient('facebook_plugin_api_error');
1993
+
1994
+ if ($error_msg) {
1995
+ echo $this->get_message_html(sprintf(__('Facebook extension error: %s ',
1996
+ 'facebook-for-woocommerce'), $error_msg));
1997
+ delete_transient('facebook_plugin_api_error');
1998
+
1999
+ WC_Facebookcommerce_Utils::fblog(
2000
+ $error_msg,
2001
+ array(),
2002
+ true);
2003
+ }
2004
+
2005
+ $warning_msg = get_transient('facebook_plugin_api_warning');
2006
+
2007
+ if ($warning_msg) {
2008
+ echo $this->get_message_html(__($warning_msg, 'facebook-for-woocommerce'),
2009
+ 'warning');
2010
+ delete_transient('facebook_plugin_api_warning');
2011
+ }
2012
+
2013
+ $success_msg = get_transient('facebook_plugin_api_success');
2014
+
2015
+ if ($success_msg) {
2016
+ echo $this->get_message_html(__($success_msg, 'facebook-for-woocommerce'),
2017
+ 'success');
2018
+ delete_transient('facebook_plugin_api_success');
2019
+ }
2020
+
2021
+ $info_msg = get_transient('facebook_plugin_api_info');
2022
+
2023
+ if ($info_msg) {
2024
+ echo $this->get_message_html(__($info_msg, 'facebook-for-woocommerce'),
2025
+ 'info');
2026
+ delete_transient('facebook_plugin_api_info');
2027
+ }
2028
+
2029
+ $sticky_msg = get_transient('facebook_plugin_api_sticky');
2030
+
2031
+ if ($sticky_msg) {
2032
+ echo $this->get_message_html(__($sticky_msg, 'facebook-for-woocommerce'),
2033
+ 'info');
2034
+ // Transient must be deleted elsewhere, or wait for timeout
2035
+ }
2036
+
2037
+ }
2038
+
2039
+ function get_page_name() {
2040
+ $page_name = '';
2041
+ if (!empty($this->settings['fb_page_id']) &&
2042
+ !empty($this->settings['fb_api_key']) ) {
2043
+
2044
+ $page_name = $this->fbgraph->get_page_name($this->settings['fb_page_id'],
2045
+ $this->settings['fb_api_key']);
2046
+ }
2047
+ return $page_name;
2048
+ }
2049
+
2050
+ function get_nux_message_ifexist() {
2051
+ $nux_type_to_elemid_map = array(
2052
+ 'messenger_chat' => 'connect_button',
2053
+ 'instagram_shopping' => 'connect_button'
2054
+ );
2055
+ $nux_type_to_message_map = array(
2056
+ 'messenger_chat' => __('Get started with Messenger Customer Chat'),
2057
+ 'instagram_shopping' => __('Get started with Instagram Shopping')
2058
+ );
2059
+ if (isset($_GET['nux'])) {
2060
+ return sprintf('<div class="nux-message" style="display: none;" data-target="%s">
2061
+ <div class="nux-message-text">%s</div>
2062
+ <div class="nux-message-arrow"></div>
2063
+ <i class="nux-message-close-btn">x</i>
2064
+ </div>
2065
+ <script>(function() { fbe_init_nux_messages(); })();</script>',
2066
+ $nux_type_to_elemid_map[sanitize_text_field($_GET['nux'])],
2067
+ $nux_type_to_message_map[sanitize_text_field($_GET['nux'])]);
2068
+ } else {
2069
+ return '';
2070
+ }
2071
+ }
2072
+
2073
+ /**
2074
+ * Admin Panel Options
2075
+ */
2076
+ function admin_options() {
2077
+ $domain = 'facebook-for-woocommerce';
2078
+ $cta_button_text = __('Get Started', $domain);
2079
+ $page_name = $this->get_page_name();
2080
+
2081
+ $can_manage = current_user_can('manage_woocommerce');
2082
+ $pre_setup = empty($this->settings['fb_page_id']) ||
2083
+ empty($this->settings['fb_api_key']);
2084
+ $apikey_invalid = !$pre_setup && $this->settings['fb_api_key'] && !$page_name;
2085
+
2086
+ $redirect_uri = '';
2087
+ $remove_http_active = is_plugin_active('remove-http/remove-http.php');
2088
+ $https_will_be_stripped = $remove_http_active &&
2089
+ !get_option('factmaven_rhttp')['external'];
2090
+ if ($https_will_be_stripped) {
2091
+ $this->display_sticky_message(__('You\'re using Remove HTTP which has
2092
+ incompatibilities with our extension. Please disable it, or select the
2093
+ "Ignore external links" option on the Remove HTTP settings page.'));
2094
+ }
2095
+
2096
+ if (!$pre_setup) {
2097
+ $cta_button_text = __('Create Ad', $domain);
2098
+ $redirect_uri = 'https://www.facebook.com/ads/dia/redirect/?settings_id='
2099
+ . $this->external_merchant_settings_id . '&version=2' .
2100
+ '&entry_point=admin_panel';
2101
+ }
2102
+ $currently_syncing = get_transient(self::FB_SYNC_IN_PROGRESS);
2103
+ $connected = ($page_name != '');
2104
+ $hide_test = ($connected && $currently_syncing) || !defined('WP_DEBUG') ||
2105
+ WP_DEBUG !== true;
2106
+ $nux_message = $this->get_nux_message_ifexist();
2107
+ ?>
2108
+ <h2><?php _e('Facebook', $domain); ?></h2>
2109
+ <p><?php _e('Control how WooCommerce integrates with your Facebook store.',
2110
+ $domain);?>
2111
+ </p>
2112
+ <hr/>
2113
+
2114
+ <div id="fbsetup">
2115
+ <div class="wrapper">
2116
+ <header>
2117
+ <div class="help-center">
2118
+ <a href="https://www.facebook.com/business/help/900699293402826" target="_blank">Help Center <i class="help-center-icon"></i></a>
2119
+ </div>
2120
+ </header>
2121
+ <div class="content">
2122
+ <h1 id="setup_h1">
2123
+ <?php
2124
+ $pre_setup
2125
+ ? _e('Grow your business on Facebook', $domain)
2126
+ : _e('Reach The Right People and Sell More Online', $domain);
2127
+ ?>
2128
+ </h1>
2129
+ <h2>
2130
+ <?php _e('Use this WooCommerce and Facebook integration to:',
2131
+ $domain); ?>
2132
+ </h2>
2133
+ <ul>
2134
+ <li id="setup_l1">
2135
+ <?php
2136
+ $pre_setup
2137
+ ? _e('Easily install a tracking pixel', $domain)
2138
+ : _e('Create an ad in a few steps', $domain);
2139
+ ?>
2140
+ </li>
2141
+ <li id="setup_l2">
2142
+ <?php
2143
+ $pre_setup
2144
+ ? _e('Upload your products and create a shop', $domain)
2145
+ : _e('Use built-in best practices for online sales', $domain);
2146
+ ?>
2147
+ </li>
2148
+ <li id="setup_l3">
2149
+ <?php
2150
+ $pre_setup
2151
+ ? _e('Create dynamic ads with your products and pixel', $domain)
2152
+ : _e('Get reporting on sales and revenue', $domain);
2153
+ ?>
2154
+ </li>
2155
+ </ul>
2156
+ <span
2157
+ <?php
2158
+ if ($pre_setup) {
2159
+ if (!$can_manage) {
2160
+ echo ' style="pointer-events: none;"';
2161
+ }
2162
+ echo '><a href="#" class="btn pre-setup" onclick="facebookConfig()"
2163
+ id="cta_button">' . esc_html($cta_button_text) . '</a></span>';
2164
+ } else {
2165
+ if (!$can_manage || $apikey_invalid ||
2166
+ !isset($this->external_merchant_settings_id)) {
2167
+ echo ' style="pointer-events: none;"';
2168
+ }
2169
+ echo (
2170
+ '><a href='.$redirect_uri.' class="btn" id="cta_button">' .
2171
+ esc_html($cta_button_text) . '</a>' .
2172
+ '<a href="https://www.facebook.com/business/m/drive-more-online-sales"
2173
+ class="btn grey" id="learnmore_button">' . __("Learn More") .
2174
+ '</a></span>'
2175
+ );
2176
+ }
2177
+ ?>
2178
+ <hr />
2179
+ <div class="settings-container">
2180
+ <div id="plugins" class="settings-section"
2181
+ <?php echo ($pre_setup && $can_manage) ? ' style="display:none;"' : ''; ?>
2182
+ >
2183
+ <h1><?php echo __('Add Ways for People to Shop'); ?></h1>
2184
+ <h2><?php echo __('Connect your business with features such as Messenger and more.'); ?></h2>
2185
+ <a href="#" class="btn small" onclick="facebookConfig()" id="connect_button">
2186
+ <?php echo __('Add Features'); ?>
2187
+ </a>
2188
+ </div>
2189
+ <div id="settings" class="settings-section"
2190
+ <?php
2191
+ if ($pre_setup && $can_manage) {
2192
+ echo ' style="display:none;"';
2193
+ }
2194
+ echo '><h1>' . esc_html__('Settings', $domain) . '</h1>';
2195
+ if ($apikey_invalid) {
2196
+ // API key is set, but no page name.
2197
+ echo '<h2 id="token_text" style="color:red;">' .
2198
+ __('Your API key is no longer valid. Please click "Settings >
2199
+ Advanced Options > Update Token".', $domain) . '</h2>
2200
+
2201
+ <span><a href="#" class="btn small" onclick="facebookConfig()"
2202
+ id="setting_button">' . __('Settings', $domain) . '</a>
2203
+ </span>';
2204
+ } else {
2205
+ if (!$can_manage) {
2206
+ echo '<h2 style="color:red;">' . __('You must have
2207
+ "manage_woocommerce" permissions to use this plugin.', $domain) .
2208
+ '</h2>';
2209
+ } else {
2210
+ echo '<h2><span id="connection_status"';
2211
+ if (!$connected) {
2212
+ echo ' style="display: none;"';
2213
+ }
2214
+ echo '>';
2215
+ echo __('Your WooCommerce store is connected to ', $domain) .
2216
+ (($page_name != '')
2217
+ ? sprintf(
2218
+ __('the Facebook page <a target="_blank" href="https://www.facebook.com/%1$s">%2$s</a></span>', $domain),
2219
+ $this->settings['fb_page_id'],
2220
+ esc_html($page_name))
2221
+ : sprintf(
2222
+ __('<a target="_blank" href="https://www.facebook.com/%1$s">your Facebook page</a></span>', $domain),
2223
+ $this->settings['fb_page_id'])
2224
+ ) .
2225
+ '.<span id="sync_complete" style="margin-left: 5px;';
2226
+ if (!$connected || $currently_syncing) {
2227
+ echo ' display: none;';
2228
+ }
2229
+ echo '">' . __('Status', $domain) . ': '
2230
+ . __('Products are synced to Facebook.', $domain) . '</span>'.
2231
+ sprintf(__('<span><a href="#" onclick="show_debug_info()"
2232
+ id="debug_info" style="display:none;" > More Info </a></span>',
2233
+ $domain)) . '</span></h2>
2234
+ <span><a href="#" class="btn small" onclick="facebookConfig()"
2235
+ id="setting_button"';
2236
+
2237
+ if ($currently_syncing) {
2238
+ echo ' style="pointer-events: none;" ';
2239
+ }
2240
+ echo '>' . __('Manage Settings', $domain) . '</a></span>
2241
+
2242
+ <span><a href="#" class="btn small" onclick="sync_confirm()"
2243
+ id="resync_products"';
2244
+
2245
+ if ($connected && $currently_syncing) {
2246
+ echo ' style="pointer-events: none;" ';
2247
+ }
2248
+ echo '>' . __('Sync Products', $domain) . '</a></span>
2249
+
2250
+ <p id="sync_progress">';
2251
+ if ($connected && $currently_syncing) {
2252
+ echo '<hr/>';
2253
+ echo __('Syncing... Keep this browser open', $domain);
2254
+ echo '<br/>';
2255
+ echo __('Until sync is complete', $domain);
2256
+ }
2257
+ echo '</p>';
2258
+ }
2259
+ } ?>
2260
+ </div>
2261
+ <hr />
2262
+ </div>
2263
+ <?php echo $nux_message; ?>
2264
+
2265
+ <div>
2266
+ <div id='fbAdvancedOptionsText' onclick="toggleAdvancedOptions();">
2267
+ Show Advanced Settings
2268
+ </div>
2269
+ <div id='fbAdvancedOptions'>
2270
+ <div class='autosync' title="This experimental feature will call force resync at the specified time using wordpress cron scheduling.">
2271
+ <input type="checkbox"
2272
+ onclick="saveAutoSyncSchedule()"
2273
+ class="autosyncCheck"
2274
+ <?php echo get_option('woocommerce_fb_autosync_time', false) ? 'checked' : 'unchecked'; ?>>
2275
+ Automatically Force Resync of Products At
2276
+
2277
+ <input
2278
+ type="time"
2279
+ value="<?php echo get_option('woocommerce_fb_autosync_time', '23:00'); ?>"
2280
+ class="autosyncTime"
2281
+ onfocusout="saveAutoSyncSchedule()"
2282
+ <?php echo get_option('woocommerce_fb_autosync_time', 0) ? '' : 'disabled'; ?> />
2283
+ Every Day.
2284
+ <span class="autosyncSavedNotice" disabled> Saved </span>
2285
+ </div>
2286
+ <div title="This option is meant for development and testing environments.">
2287
+ <input type="checkbox"
2288
+ onclick="onSetDisableSyncOnDevEnvironment()"
2289
+ class="disableOnDevEnvironment"
2290
+ <?php echo get_option('fb_disable_sync_on_dev_environment', false)
2291
+ ? 'checked'
2292
+ : 'unchecked'; ?> />
2293
+ Disable Product Sync with FB
2294
+ </div>
2295
+ <div class='shortdescr' title="This experimental feature will import short description instead of description for all products.">
2296
+ <input type="checkbox"
2297
+ onclick="syncShortDescription()"
2298
+ class="syncShortDescription"
2299
+ <?php echo get_option('fb_sync_short_description', false)
2300
+ ? 'checked'
2301
+ : 'unchecked'; ?> />
2302
+ Sync Short Description Instead of Description
2303
+ </div>
2304
+ </div>
2305
+ </div>
2306
+ </div>
2307
+ </div>
2308
+ <div <?php echo ($hide_test) ? ' style="display:none;" ' : ''; ?> >
2309
+ <p class="tooltip" id="test_product_sync">
2310
+ <?php
2311
+ // WP_DEBUG mode: button to launch test
2312
+ echo sprintf(__('<a href="%s&fb_test_product_sync=true"', $domain),
2313
+ WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL);
2314
+ echo '>' . esc_html__('Launch Test', $domain);
2315
+ ?>
2316
+ <span class='tooltiptext'>
2317
+ <?php
2318
+ _e('This button will run an integration test suite verifying the
2319
+ extension. Note that this will reset your products and resync them
2320
+ to Facebook. Not recommended to use unless you are changing the
2321
+ extension code and want to test your changes.', $domain);
2322
+ ?>
2323
+ </span>
2324
+ <?php
2325
+ echo '</a>';
2326
+ ?>
2327
+ </p>
2328
+ <p id="stack_trace"></p>
2329
+ </div>
2330
+ <br/><hr/><br/>
2331
+ <?php
2332
+
2333
+ $GLOBALS['hide_save_button'] = true;
2334
+ if (defined('WP_DEBUG') && true === WP_DEBUG) {
2335
+ $GLOBALS['hide_save_button'] = false;
2336
+ ?>
2337
+ <table class="form-table">
2338
+ <?php $this->generate_settings_html(); ?>
2339
+ </table><!--/.form-table-->
2340
+ <?php
2341
+ }
2342
+ }
2343
+
2344
+ function delete_product_item($wp_id) {
2345
+ $fb_product_item_id = $this->get_product_fbid(
2346
+ self::FB_PRODUCT_ITEM_ID,
2347
+ $wp_id);
2348
+ if ($fb_product_item_id) {
2349
+ $pi_result =
2350
+ $this->fbgraph->delete_product_item($fb_product_item_id);
2351
+ WC_Facebookcommerce_Utils::log($pi_result);
2352
+ }
2353
+ }
2354
+
2355
+ function fb_duplicate_product_reset_meta($to_delete) {
2356
+ array_push($to_delete, self::FB_PRODUCT_ITEM_ID);
2357
+ array_push($to_delete, self::FB_PRODUCT_GROUP_ID);
2358
+ return $to_delete;
2359
+ }
2360
+
2361
+ /**
2362
+ * Helper function to update FB visibility.
2363
+ */
2364
+ function update_fb_visibility($wp_id, $visibility) {
2365
+ $woo_product = new WC_Facebook_Product($wp_id);
2366
+ if (!$woo_product->exists()) {
2367
+ // This function can be called for non-woo products.
2368
+ return;
2369
+ }
2370
+
2371
+ $products = WC_Facebookcommerce_Utils::get_product_array($woo_product);
2372
+ foreach ($products as $item_id) {
2373
+ $fb_product_item_id = $this->get_product_fbid(
2374
+ self::FB_PRODUCT_ITEM_ID,
2375
+ $item_id);
2376
+
2377
+ if (!$fb_product_item_id) {
2378
+ WC_Facebookcommerce_Utils::fblog(
2379
+ $fb_product_item_id." doesn't exist but underwent a visibility transform.",
2380
+ array(),
2381
+ true);
2382
+ continue;
2383
+ }
2384
+ $result = $this->check_api_result(
2385
+ $this->fbgraph->update_product_item(
2386
+ $fb_product_item_id,
2387
+ array('visibility' => $visibility)));
2388
+ if ($result) {
2389
+ update_post_meta($item_id, self::FB_VISIBILITY, $visibility);
2390
+ update_post_meta($wp_id, self::FB_VISIBILITY, $visibility);
2391
+ }
2392
+ }
2393
+ }
2394
+
2395
+ function on_quick_and_bulk_edit_save($product) {
2396
+ $wp_id = $product->get_id();
2397
+ $visibility = get_post_status($wp_id) == 'publish'
2398
+ ? 'published'
2399
+ : 'staging';
2400
+ // case 1: new status is 'publish' regardless of old status, sync to FB
2401
+ if ($visibility == 'published') {
2402
+ $this->on_product_publish($wp_id);
2403
+ } else {
2404
+ // case 2: product never publish to FB, new status is not publish
2405
+ // case 3: product new status is not publish and published before
2406
+ $this->update_fb_visibility($wp_id, $visibility);
2407
+ }
2408
+ }
2409
+
2410
+ private function get_product_fbid($fbid_type, $wp_id, $woo_product = null) {
2411
+ $fb_id = WC_Facebookcommerce_Utils::get_fbid_post_meta(
2412
+ $wp_id,
2413
+ $fbid_type);
2414
+ if ($fb_id) {
2415
+ return $fb_id;
2416
+ }
2417
+ if (!isset($this->settings['upload_end_time'])) {
2418
+ return null;
2419
+ }
2420
+ if (!$woo_product) {
2421
+ $woo_product = new WC_Facebook_Product($wp_id);
2422
+ }
2423
+ $products = WC_Facebookcommerce_Utils::get_product_array($woo_product);
2424
+ $woo_product = new WC_Facebook_Product(current($products));
2425
+ // This is a generalized function used elsewhere
2426
+ // Cannot call is_hidden for VC_Product_Variable Object
2427
+ if ($woo_product->is_hidden()) {
2428
+ return null;
2429
+ }
2430
+ $fb_retailer_id =
2431
+ WC_Facebookcommerce_Utils::get_fb_retailer_id($woo_product);
2432
+
2433
+ $product_fbid_result = $this->fbgraph->get_facebook_id(
2434
+ $this->product_catalog_id,
2435
+ $fb_retailer_id);
2436
+ if (is_wp_error($product_fbid_result)) {
2437
+ WC_Facebookcommerce_Utils::log($product_fbid_result->get_error_message());
2438
+ $this->display_error_message(
2439
+ "There was an issue connecting to the Facebook API: ".
2440
+ $product_fbid_result->get_error_message());
2441
+ return;
2442
+ }
2443
+
2444
+ if ($product_fbid_result && isset($product_fbid_result['body'])) {
2445
+ $body = WC_Facebookcommerce_Utils::decode_json($product_fbid_result['body']);
2446
+ if ($body && $body->id) {
2447
+ if ($fbid_type == self::FB_PRODUCT_GROUP_ID) {
2448
+ $fb_id = $body->product_group->id;
2449
+ } else {
2450
+ $fb_id = $body->id;
2451
+ }
2452
+ update_post_meta(
2453
+ $wp_id,
2454
+ $fbid_type,
2455
+ $fb_id);
2456
+ update_post_meta($wp_id, self::FB_VISIBILITY, true);
2457
+ return $fb_id;
2458
+ }
2459
+ }
2460
+ return;
2461
+ }
2462
+
2463
+ private function set_default_variant($product_group_id, $product_item_id) {
2464
+ $result = $this->check_api_result(
2465
+ $this->fbgraph->set_default_variant(
2466
+ $product_group_id,
2467
+ array('default_product_id' => $product_item_id)));
2468
+ if (!$result) {
2469
+ WC_Facebookcommerce_Utils::fblog(
2470
+ 'Fail to set default product item',
2471
+ array(),
2472
+ true);
2473
+ }
2474
+ }
2475
+
2476
+ private function fb_wp_die() {
2477
+ if (!$this->test_mode) {
2478
+ wp_die();
2479
+ }
2480
+ }
2481
+
2482
+ /**
2483
+ * Display test result.
2484
+ **/
2485
+ function ajax_display_test_result() {
2486
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('test result', true);
2487
+ $response = array(
2488
+ 'pass' => 'true',
2489
+ );
2490
+ $test_pass = get_option('fb_test_pass', null);
2491
+ if (!isset($test_pass)) {
2492
+ $response['pass'] = 'in progress';
2493
+ } else if ($test_pass == 0) {
2494
+ $response['pass'] = 'false';
2495
+ $response['debug_info'] = get_transient('facebook_plugin_test_fail');
2496
+ $response['stack_trace'] =
2497
+ get_transient('facebook_plugin_test_stack_trace');
2498
+ $response['stack_trace'] =
2499
+ preg_replace("/\n/", '<br>', $response['stack_trace']);
2500
+ delete_transient('facebook_plugin_test_fail');
2501
+ delete_transient('facebook_plugin_test_stack_trace');
2502
+ }
2503
+ delete_option('fb_test_pass');
2504
+ printf(json_encode($response));
2505
+ wp_die();
2506
+ }
2507
+
2508
+ /**
2509
+ * Schedule Force Resync
2510
+ */
2511
+ function ajax_schedule_force_resync() {
2512
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('resync schedule', true);
2513
+ if (isset($_POST) && isset($_POST['enabled'])) {
2514
+ if (isset($_POST['time']) && $_POST['enabled']) { // Enabled
2515
+ $time = sanitize_text_field($_POST['time']);
2516
+ wp_clear_scheduled_hook('sync_all_fb_products_using_feed');
2517
+ wp_schedule_event(
2518
+ strtotime($time),
2519
+ 'daily',
2520
+ 'sync_all_fb_products_using_feed');
2521
+ WC_Facebookcommerce_Utils::fblog('Scheduled autosync for '.$time);
2522
+ update_option('woocommerce_fb_autosync_time', $time);
2523
+ } else if (!$_POST['enabled']) { // Disabled
2524
+ wp_clear_scheduled_hook('sync_all_fb_products_using_feed');
2525
+ WC_Facebookcommerce_Utils::fblog('Autosync disabled');
2526
+ delete_option('woocommerce_fb_autosync_time');
2527
+ }
2528
+ } else {
2529
+ WC_Facebookcommerce_Utils::fblog('Autosync AJAX Problem', $_POST, true);
2530
+ }
2531
+ wp_die();
2532
+ }
2533
+
2534
+ function ajax_update_fb_option() {
2535
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions('update fb options', true);
2536
+ if (isset($_POST) && stripos($_POST['option'], 'fb_') === 0) {
2537
+ update_option(sanitize_text_field($_POST['option']), sanitize_text_field($_POST['option_value']));
2538
+ }
2539
+ wp_die();
2540
+ }
2541
+ }
facebook-config-warmer.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!defined('ABSPATH')) exit; // Exit if accessed directly
12
+
13
+
14
+ if (!class_exists('WC_Facebookcommerce_WarmConfig')) :
15
+
16
+ class WC_Facebookcommerce_WarmConfig {
17
+ static $fb_warm_pixel_id = null;
18
+ }
19
+
20
+ endif;
facebook-for-woocommerce.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * Plugin Name: Facebook for WooCommerce
9
+ * Plugin URI: https://github.com/facebookincubator/facebook-for-woocommerce/
10
+ * Description: Grow your business on Facebook! Use this official plugin to help sell more of your products using Facebook. After completing the setup, you'll be ready to create ads that promote your products and you can also create a shop section on your Page where customers can browse your products on Facebook.
11
+ * Author: Facebook
12
+ * Author URI: https://www.facebook.com/
13
+ * Version: 1.9.11
14
+ * Woo: 2127297:0ea4fe4c2d7ca6338f8a322fb3e4e187
15
+ * Text Domain: facebook-for-woocommerce
16
+ * WC requires at least: 3.0.0
17
+ * WC tested up to: 3.3.5
18
+ *
19
+ * @package FacebookCommerce
20
+ */
21
+
22
+
23
+ if (!class_exists('WC_Facebookcommerce')) :
24
+ include_once 'includes/fbutils.php';
25
+
26
+ class WC_Facebookcommerce {
27
+
28
+ // Change it above as well
29
+ const PLUGIN_VERSION = WC_Facebookcommerce_Utils::PLUGIN_VERSION;
30
+
31
+ /**
32
+ * Construct the plugin.
33
+ */
34
+ public function __construct() {
35
+ add_action('plugins_loaded', array( $this, 'init'));
36
+ }
37
+
38
+ /**
39
+ * Initialize the plugin.
40
+ */
41
+ public function init() {
42
+ if (is_admin()) {
43
+ add_filter('plugin_action_links_'.plugin_basename(__FILE__),
44
+ array($this, 'add_settings_link'));
45
+ }
46
+
47
+ if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
48
+ if (!defined('WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL')) {
49
+ define(
50
+ 'WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL',
51
+ get_admin_url()
52
+ .'/admin.php?page=wc-settings&tab=integration'
53
+ .'&section=facebookcommerce');
54
+ }
55
+ include_once 'facebook-commerce.php';
56
+
57
+ // Register WooCommerce integration.
58
+ add_filter('woocommerce_integrations', array(
59
+ $this,
60
+ 'add_woocommerce_integration'
61
+ ));
62
+ }
63
+ }
64
+
65
+ public function add_settings_link($links) {
66
+ $settings = array(
67
+ 'settings' => sprintf(
68
+ '<a href="%s">%s</a>',
69
+ admin_url('admin.php?page=wc-settings&tab=integration&section=facebookcommerce'),
70
+ 'Settings')
71
+ );
72
+ return array_merge($settings, $links);
73
+ }
74
+
75
+ public function wp_debug_display_error() {
76
+ ?>
77
+ <div class="error below-h3">
78
+ <p>
79
+ <?php
80
+ printf(__('To use Facebook for WooCommerce,
81
+ please disable WP_DEBUG_DISPLAY in your wp-config.php file.
82
+ Contact your server administrator for more assistance.',
83
+ 'facebook-for-woocommerce'));
84
+ ?>
85
+ </p>
86
+ </div>
87
+ <?php
88
+ }
89
+
90
+ /**
91
+ * Add a new integration to WooCommerce.
92
+ */
93
+ public function add_woocommerce_integration($integrations) {
94
+ $integrations[] = 'WC_Facebookcommerce_Integration';
95
+ return $integrations;
96
+ }
97
+
98
+ public function add_wordpress_integration() {
99
+ new WP_Facebook_Integration();
100
+ }
101
+ }
102
+
103
+ $WC_Facebookcommerce = new WC_Facebookcommerce(__FILE__);
104
+
105
+ endif;
includes/fbasync.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (!class_exists('WP_Async_Request', false) ) {
16
+ // Do not attempt to create this class without WP_Async_Request
17
+ return;
18
+ }
19
+
20
+ if (!class_exists('WC_Facebookcommerce_Async_Request')) :
21
+
22
+ /**
23
+ * FB Graph API async request
24
+ *
25
+ */
26
+ class WC_Facebookcommerce_Async_Request extends WP_Async_Request {
27
+
28
+ protected $action = 'wc_facebook_async_request';
29
+
30
+ /**
31
+ * Handle
32
+ *
33
+ * Override this method to perform any actions required
34
+ * during the async request.
35
+ */
36
+ protected function handle() {
37
+ // Actions to perform
38
+ }
39
+
40
+ }
41
+
42
+ endif;
includes/fbbackground.php ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (! defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (!class_exists('WP_Background_Process', false) ) {
16
+ // Do not attempt to create this class without WP_Background_Process
17
+ return;
18
+ }
19
+
20
+ if (! class_exists('WC_Facebookcommerce_Background_Process')) :
21
+
22
+ class WC_Facebookcommerce_Background_Process extends WP_Background_Process {
23
+
24
+ public function __construct($commerce) {
25
+ $this->commerce = $commerce; // Full WC_Facebookcommerce_Integration obj
26
+ }
27
+
28
+ /**
29
+ * @var string
30
+ */
31
+ protected $action = 'fb_commerce_background_process';
32
+
33
+ public function dispatch() {
34
+ $commerce = $this->commerce;
35
+ $dispatched = parent::dispatch();
36
+
37
+ if (is_wp_error($dispatched)) {
38
+ WC_Facebookcommerce_Utils::log(
39
+ sprintf('Unable to dispatch FB Background processor: %s',
40
+ $dispatched->get_error_message()),
41
+ array( 'source' => 'wc_facebook_background_process' ));
42
+ }
43
+ }
44
+
45
+ public function get_item_count() {
46
+ $commerce = $this->commerce;
47
+ return (int)get_transient($commerce::FB_SYNC_REMAINING);
48
+ }
49
+
50
+ /**
51
+ * Handle cron healthcheck
52
+ *
53
+ * Restart the background process if not already running
54
+ * and data exists in the queue.
55
+ */
56
+ public function handle_cron_healthcheck() {
57
+ $commerce = $this->commerce;
58
+ if ($this->is_process_running()) {
59
+ // Background process already running, no-op
60
+ return true; // Return "is running? status"
61
+ }
62
+
63
+ if ($this->is_queue_empty()) {
64
+ // No data to process.
65
+ $this->clear_scheduled_event();
66
+ delete_transient($commerce::FB_SYNC_REMAINING);
67
+ return;
68
+ }
69
+
70
+ $this->handle();
71
+ return true;
72
+
73
+ }
74
+
75
+ /**
76
+ * Schedule fallback event.
77
+ */
78
+ protected function schedule_event() {
79
+ if (!wp_next_scheduled($this->cron_hook_identifier)) {
80
+ wp_schedule_event(time() + 10, $this->cron_interval_identifier,
81
+ $this->cron_hook_identifier);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Is the processor updating?
87
+ * @return boolean
88
+ */
89
+ public function is_updating() {
90
+ return false === $this->is_queue_empty();
91
+ }
92
+
93
+ /**
94
+ * Is the processor running?
95
+ * @return boolean
96
+ */
97
+ public function is_running() {
98
+ return $this->is_process_running();
99
+ }
100
+
101
+ /**
102
+ * Process individual product
103
+ *
104
+ * Returns false to remove the item from the queue
105
+ * (would return item if it needed additional processing).
106
+ *
107
+ * @param mixed $item Queue item to iterate over
108
+ *
109
+ * @return mixed
110
+ */
111
+ protected function task($item) {
112
+ $commerce = $this->commerce; // PHP5 compatibility for static access
113
+ $remaining = $this->get_item_count();
114
+ $count_message = sprintf(
115
+ 'Background syncing products to Facebook. Products remaining: %1$d',
116
+ $remaining);
117
+
118
+ $this->commerce->display_sticky_message($count_message, true);
119
+
120
+ $this->commerce->on_product_publish($item);
121
+ $remaining--;
122
+ set_transient(
123
+ $commerce::FB_SYNC_IN_PROGRESS,
124
+ true,
125
+ $commerce::FB_SYNC_TIMEOUT);
126
+ set_transient(
127
+ $commerce::FB_SYNC_REMAINING,
128
+ $remaining);
129
+
130
+ return false;
131
+ }
132
+
133
+ /**
134
+ * Complete
135
+ *
136
+ * Override if applicable, but ensure that the below actions are
137
+ * performed, or, call parent::complete().
138
+ */
139
+ protected function complete() {
140
+ $commerce = $this->commerce; // PHP5 compatibility for static access
141
+ delete_transient($commerce::FB_SYNC_IN_PROGRESS);
142
+ delete_transient($commerce::FB_SYNC_REMAINING);
143
+ WC_Facebookcommerce_Utils::log("Background sync complete!");
144
+ WC_Facebookcommerce_Utils::fblog("Background sync complete!");
145
+ $this->commerce->remove_sticky_message();
146
+ $this->commerce->display_info_message('Facebook product sync complete!');
147
+ parent::complete();
148
+ }
149
+
150
+ }
151
+
152
+ endif;
includes/fbcustomapi.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (! defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (! class_exists('WC_Facebookcommerce_REST_Controller')) :
16
+
17
+ /**
18
+ * Custom API REST path class
19
+ *
20
+ */
21
+ class WC_Facebookcommerce_REST_Controller extends WP_REST_Controller {
22
+
23
+ /**
24
+ * Init and hook in the integration.
25
+ */
26
+ public function __construct() {
27
+ global $woocommerce;
28
+ add_action('rest_api_init', array($this, 'register_routes'));
29
+
30
+
31
+ // TODO: wp_woocommerce_api_keys
32
+ // http://stackoverflow.com/questions/31327994/woocommerce-rest-client-api-
33
+ // programmatically-get-consumer-key-and-secret
34
+
35
+ }
36
+
37
+ /**
38
+ * Function to define custom routes
39
+ */
40
+ public function register_routes() {
41
+ register_rest_route('facebook/v1', '/version' ,
42
+ array(
43
+ 'methods' => WP_REST_Server::READABLE,
44
+ 'callback' => array($this, 'fb_test_function'),
45
+ ));
46
+ }
47
+
48
+ public function fb_test_function(WP_REST_Request $request) {
49
+ $parameters = $request->get_params();
50
+ // Create the response object
51
+ $res = new WP_REST_Response(WC_Facebookcommerce_Utils::PLUGIN_VERSION);
52
+
53
+ // Add a custom status code
54
+ $res->set_status(200);
55
+ $res->jsonSerialize();
56
+
57
+ return $res;
58
+ }
59
+
60
+
61
+ }
62
+
63
+ endif;
includes/fbgraph.php ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (!class_exists('WC_Facebookcommerce_Graph_API')) :
16
+
17
+ if (!class_exists('WC_Facebookcommerce_Async_Request')) {
18
+ include_once 'fbasync.php';
19
+ }
20
+
21
+ /**
22
+ * FB Graph API helper functions
23
+ *
24
+ */
25
+ class WC_Facebookcommerce_Graph_API {
26
+ const GRAPH_API_URL = 'https://graph.facebook.com/v2.9/';
27
+ const CURL_TIMEOUT = 500;
28
+
29
+ /**
30
+ * Cache the api_key
31
+ */
32
+ var $api_key;
33
+
34
+ /**
35
+ * Init
36
+ */
37
+ public function __construct($api_key) {
38
+ $this->api_key = $api_key;
39
+ }
40
+
41
+ public function _get($url, $api_key = '') {
42
+ $api_key = $api_key ?: $this->api_key;
43
+ return wp_remote_get($url, array(
44
+ 'headers' => array(
45
+ 'Authorization' => 'Bearer ' . $api_key,
46
+ ),
47
+ 'timeout' => self::CURL_TIMEOUT,
48
+ ));
49
+ }
50
+
51
+ public function _post($url, $data, $api_key = '') {
52
+ if (class_exists('WC_Facebookcommerce_Async_Request')) {
53
+ return self::_post_async($url, $data);
54
+ } else {
55
+ return self::_post_sync($url, $data);
56
+ }
57
+ }
58
+
59
+ public function _post_sync($url, $data, $api_key = '') {
60
+ $api_key = $api_key ?: $this->api_key;
61
+ return wp_remote_post($url, array(
62
+ 'body' => $data,
63
+ 'headers' => array(
64
+ 'Authorization' => 'Bearer ' . $api_key,
65
+ ),
66
+ 'timeout' => self::CURL_TIMEOUT,
67
+ ));
68
+ }
69
+
70
+ public function _post_async($url, $data, $api_key = '') {
71
+ if (!class_exists('WC_Facebookcommerce_Async_Request')) {
72
+ return;
73
+ }
74
+
75
+ $api_key = $api_key ?: $this->api_key;
76
+ $fbasync = new WC_Facebookcommerce_Async_Request();
77
+
78
+ $fbasync->query_url = $url;
79
+ $fbasync->query_args = array();
80
+ $fbasync->post_args = array(
81
+ 'body' => $data,
82
+ 'headers' => array(
83
+ 'Authorization' => 'Bearer ' . $api_key,
84
+ ),
85
+ 'timeout' => self::CURL_TIMEOUT,
86
+ );
87
+
88
+ return $fbasync->dispatch();
89
+ }
90
+
91
+ public function _delete($url, $api_key = '') {
92
+ $api_key = $api_key ?: $this->api_key;
93
+
94
+ return wp_remote_request($url, array(
95
+ 'headers' => array(
96
+ 'Authorization' => 'Bearer ' . $api_key,
97
+ ),
98
+ 'timeout' => self::CURL_TIMEOUT,
99
+ 'method' => 'DELETE',
100
+ ));
101
+ }
102
+
103
+ // GET https://graph.facebook.com/vX.X/{page-id}/?fields=name
104
+ public function get_page_name($page_id, $api_key = '') {
105
+ $api_key = $api_key ?: $this->api_key;
106
+ $url = $this->build_url($page_id, '/?fields=name');
107
+ $response = self::_get($url, $api_key);
108
+ if (is_wp_error($response)) {
109
+ WC_Facebookcommerce_Utils::log($response->get_error_message());
110
+ return;
111
+ }
112
+ if ($response['response']['code'] != '200') {
113
+ return;
114
+ }
115
+
116
+ $response_body = wp_remote_retrieve_body($response);
117
+
118
+ return json_decode($response_body)->name;
119
+ }
120
+
121
+ public function validate_product_catalog($product_catalog_id) {
122
+ $url = $this->build_url($product_catalog_id);
123
+ $response = self::_get($url);
124
+ if (is_wp_error($response)) {
125
+ WC_Facebookcommerce_Utils::log($response->get_error_message());
126
+ return;
127
+ }
128
+ return $response['response']['code'] == '200';
129
+ }
130
+
131
+ // POST https://graph.facebook.com/vX.X/{product-catalog-id}/product_groups
132
+ public function create_product_group($product_catalog_id, $data) {
133
+ $url = $this->build_url($product_catalog_id, '/product_groups');
134
+ return self::_post($url, $data);
135
+ }
136
+
137
+ // POST https://graph.facebook.com/vX.X/{product-group-id}/products
138
+ public function create_product_item($product_group_id, $data) {
139
+ $url = $this->build_url($product_group_id, '/products');
140
+ return self::_post($url, $data);
141
+ }
142
+
143
+ public function update_product_group($product_catalog_id, $data) {
144
+ $url = $this->build_url($product_catalog_id);
145
+ return self::_post($url, $data);
146
+ }
147
+
148
+ public function update_product_item($product_id, $data) {
149
+ $url = $this->build_url($product_id);
150
+ return self::_post($url, $data);
151
+ }
152
+
153
+ public function delete_product_item($product_item_id) {
154
+ $product_item_url = $this->build_url($product_item_id);
155
+ return self::_delete($product_item_url);
156
+ }
157
+
158
+ public function delete_product_group($product_group_id) {
159
+ $product_group_url = $this->build_url($product_group_id);
160
+ return self::_delete($product_group_url);
161
+ }
162
+
163
+ public function log($ems_id, $message, $error) {
164
+ $log_url = $this->build_url($ems_id, '/log_events');
165
+
166
+ $data = array(
167
+ 'message'=> $message,
168
+ 'error' => $error
169
+ );
170
+
171
+ self::_post($log_url, $data);
172
+ }
173
+
174
+ public function log_tip_event($tip_id, $channel_id, $event) {
175
+ $tip_event_log_url = $this->build_url('', '/log_tip_events');
176
+
177
+ $data = array(
178
+ 'tip_id' => $tip_id,
179
+ 'channel_id' => $channel_id,
180
+ 'event' => $event
181
+ );
182
+
183
+ self::_post($tip_event_log_url, $data);
184
+ }
185
+
186
+ public function create_upload($facebook_feed_id, $path_to_feed_file) {
187
+ $url = $this->build_url(
188
+ $facebook_feed_id,
189
+ '/uploads?access_token=' . $this->api_key);
190
+ $data = array(
191
+ 'file' => new CurlFile($path_to_feed_file, 'text/csv')
192
+ );
193
+ $curl = curl_init();
194
+ curl_setopt_array(
195
+ $curl,
196
+ array(
197
+ CURLOPT_URL => $url,
198
+ CURLOPT_POST => 1,
199
+ CURLOPT_POSTFIELDS => $data,
200
+ CURLOPT_RETURNTRANSFER => 1));
201
+ $response = curl_exec($curl);
202
+ if (curl_errno($curl)) {
203
+ WC_Facebookcommerce_Utils::fblog($response);
204
+ return null;
205
+ }
206
+ return WC_Facebookcommerce_Utils::decode_json($response, true);
207
+ }
208
+
209
+ public function create_feed($facebook_catalog_id, $data) {
210
+ $url = $this->build_url($facebook_catalog_id, '/product_feeds');
211
+ // success API call will return {id: <product feed id>}
212
+ // failure API will return {error: <error message>}
213
+ return self::_post($url, $data);
214
+ }
215
+
216
+ public function get_upload_status($facebook_upload_id) {
217
+ $url = $this->build_url($facebook_upload_id, '/?fields=end_time');
218
+ // success API call will return
219
+ // {id: <upload id>, end_time: <time when upload completes>}
220
+ // failure API will return {error: <error message>}
221
+ return self::_get($url);
222
+ }
223
+
224
+ // success API call will return a JSON of tip info
225
+ public function get_tip_info($external_merchant_settings_id) {
226
+ $url = $this->build_url($external_merchant_settings_id, '/?fields=connect_woo');
227
+ $response = self::_get($url, $this->api_key);
228
+ $data = array(
229
+ 'response' => $response,
230
+ );
231
+ if (is_wp_error($response)) {
232
+ $data['error_type'] = 'is_wp_error';
233
+ WC_Facebookcommerce_Utils::fblog(
234
+ 'Failed to get AYMT tip info via API.',
235
+ $data,
236
+ true);
237
+ return;
238
+ }
239
+ if ($response['response']['code'] != '200') {
240
+ $data['error_type'] = 'Non-200 error code from FB';
241
+ WC_Facebookcommerce_Utils::fblog(
242
+ 'Failed to get AYMT tip info via API.',
243
+ $data,
244
+ true);
245
+ return;
246
+ }
247
+
248
+ $response_body = wp_remote_retrieve_body($response);
249
+ $connect_woo =
250
+ WC_Facebookcommerce_Utils::decode_json($response_body)->connect_woo;
251
+ if (!isset($connect_woo)) {
252
+ $data['error_type'] = 'Response body not set';
253
+ WC_Facebookcommerce_Utils::fblog(
254
+ "Failed to get AYMT tip info via API.",
255
+ $data,
256
+ true);
257
+ }
258
+ return $connect_woo;
259
+ }
260
+
261
+ public function get_facebook_id($facebook_catalog_id, $product_id) {
262
+ $param = 'catalog:' . (string)$facebook_catalog_id . ':' .
263
+ base64_encode($product_id) . '/?fields=id,product_group{id}';
264
+ $url = $this->build_url('', $param);
265
+ // success API call will return
266
+ // {id: <fb product id>, product_group{id} <fb product group id>}
267
+ // failure API will return {error: <error message>}
268
+ return self::_get($url);
269
+ }
270
+
271
+ public function check_product_info($facebook_catalog_id, $product_id, $pr_v) {
272
+ $param = 'catalog:' . (string)$facebook_catalog_id . ':' .
273
+ base64_encode($product_id) . '/?fields=id,name,description,price,' .
274
+ 'sale_price,sale_price_start_date,sale_price_end_date,image_url,' .
275
+ 'visibility';
276
+ if ($pr_v) {
277
+ $param = $param . ',additional_variant_attributes{value}';
278
+ }
279
+ $url = $this->build_url('', $param);
280
+ // success API call will return
281
+ // {id: <fb product id>, name,description,price,sale_price,sale_price_start_date
282
+ // sale_price_end_date
283
+ // failure API will return {error: <error message>}
284
+ return self::_get($url);
285
+ }
286
+
287
+ public function set_default_variant($product_group_id, $data) {
288
+ $url = $this->build_url($product_group_id);
289
+ return self::_post($url, $data);
290
+ }
291
+
292
+ private function build_url($field_id, $param ='') {
293
+ return self::GRAPH_API_URL . (string)$field_id . $param;
294
+ }
295
+
296
+ }
297
+
298
+ endif;
includes/fbinfobanner.php ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (! defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (!class_exists('WC_Facebookcommerce_Utils')) {
16
+ include_once 'includes/fbutils.php';
17
+ }
18
+
19
+ if (! class_exists('WC_Facebookcommerce_Info_Banner')) :
20
+
21
+ /**
22
+ * FB Info Banner class
23
+ */
24
+ class WC_Facebookcommerce_Info_Banner {
25
+
26
+ const FB_NO_TIP_EXISTS = 'No Tip Exist!';
27
+ const DEFAULT_TIP_IMG_URL_PREFIX = 'https://www.facebook.com';
28
+ const CHANNEL_ID = 2087541767986590;
29
+
30
+ /** @var object Class Instance */
31
+ private static $instance;
32
+
33
+ /** @var string If the banner has been dismissed */
34
+ private $external_merchant_settings_id;
35
+ private $fbgraph;
36
+ private $should_query_tip;
37
+
38
+ /**
39
+ * Get the class instance
40
+ */
41
+ public static function get_instance(
42
+ $external_merchant_settings_id,
43
+ $fbgraph,
44
+ $should_query_tip = false) {
45
+ return null === self::$instance
46
+ ? (self::$instance = new self(
47
+ $external_merchant_settings_id,
48
+ $fbgraph,
49
+ $should_query_tip))
50
+ : self::$instance;
51
+ }
52
+
53
+ /**
54
+ * Constructor
55
+ */
56
+ public function __construct(
57
+ $external_merchant_settings_id,
58
+ $fbgraph,
59
+ $should_query_tip = false) {
60
+ $this->should_query_tip = $should_query_tip;
61
+ $this->external_merchant_settings_id = $external_merchant_settings_id;
62
+ $this->fbgraph = $fbgraph;
63
+ add_action('wp_ajax_ajax_woo_infobanner_post_click', array($this, 'ajax_woo_infobanner_post_click'));
64
+ add_action('wp_ajax_ajax_woo_infobanner_post_xout', array($this, 'ajax_woo_infobanner_post_xout'));
65
+ add_action('admin_notices', array($this, 'banner'));
66
+ add_action('admin_init', array($this, 'dismiss_banner'));
67
+ }
68
+
69
+ /**
70
+ * Post click event when hit primary button.
71
+ */
72
+ function ajax_woo_infobanner_post_click() {
73
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
74
+ 'post tip click event',
75
+ true);
76
+ $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
77
+ $tip_id = isset($tip_info->tip_id)
78
+ ? $tip_info->tip_id
79
+ : null;
80
+ if ($tip_id == null) {
81
+ WC_Facebookcommerce_Utils::fblog(
82
+ 'Do not have tip id when click, sth went wrong',
83
+ array('tip_info' => $tip_info),
84
+ true);
85
+ } else {
86
+ WC_Facebookcommerce_Utils::tip_events_log(
87
+ $tip_id,
88
+ self::CHANNEL_ID,
89
+ 'click');
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Post xout event when hit dismiss button.
95
+ */
96
+ function ajax_woo_infobanner_post_xout() {
97
+ WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
98
+ 'post tip xout event',
99
+ true);
100
+ $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
101
+ $tip_id = isset($tip_info->tip_id)
102
+ ? $tip_info->tip_id
103
+ : null;
104
+ // Delete cached tip if xout.
105
+ update_option('fb_info_banner_last_best_tip', '');
106
+ if ($tip_id == null) {
107
+ WC_Facebookcommerce_Utils::fblog(
108
+ 'Do not have tip id when xout, sth went wrong',
109
+ array('tip_info' => $tip_info),
110
+ true);
111
+ } else {
112
+ WC_Facebookcommerce_Utils::tip_events_log(
113
+ $tip_id,
114
+ self::CHANNEL_ID,
115
+ 'xout');
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Display a info banner on Woocommerce pages.
121
+ */
122
+ public function banner() {
123
+ $screen = get_current_screen();
124
+ if (!in_array($screen->base, array('woocommerce_page_wc-reports',
125
+ 'woocommerce_page_wc-settings', 'woocommerce_page_wc-status')) ||
126
+ $screen->is_network || $screen->action) {
127
+ return;
128
+ }
129
+
130
+ $tip_info = null;
131
+ if (!$this->should_query_tip) {
132
+ // If last query is less than 1 day, either has last best tip or default
133
+ // tip pass time cap.
134
+ $tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
135
+ } else {
136
+ $tip_info = $this->fbgraph->get_tip_info(
137
+ $this->external_merchant_settings_id);
138
+ update_option('fb_info_banner_last_query_time', current_time('mysql'));
139
+ }
140
+
141
+ // Not render if no cached best tip, or no best tip returned from FB.
142
+ if (!$tip_info || ($tip_info === self::FB_NO_TIP_EXISTS)) {
143
+ // Delete cached tip if should query and get no tip.
144
+ delete_option('fb_info_banner_last_best_tip');
145
+ return;
146
+ } else {
147
+ // Get tip creatives via API
148
+ if (is_string($tip_info)) {
149
+ $tip_info = WC_Facebookcommerce_Utils::decode_json($tip_info);
150
+ }
151
+ $tip_title = isset($tip_info->tip_title->__html)
152
+ ? $tip_info->tip_title->__html
153
+ : null;
154
+
155
+ $tip_body = isset($tip_info->tip_body->__html)
156
+ ? $tip_info->tip_body->__html
157
+ : null;
158
+
159
+ $tip_action_link = isset($tip_info->tip_action_link)
160
+ ? $tip_info->tip_action_link
161
+ : null;
162
+
163
+ $tip_action = isset($tip_info->tip_action->__html)
164
+ ? $tip_info->tip_action->__html
165
+ : null;
166
+
167
+ $tip_img_url = isset($tip_info->tip_img_url)
168
+ ? self::DEFAULT_TIP_IMG_URL_PREFIX . $tip_info->tip_img_url
169
+ : null;
170
+
171
+ if ($tip_title == null || $tip_body == null || $tip_action_link == null
172
+ || $tip_action == null || $tip_action == null) {
173
+ WC_Facebookcommerce_Utils::fblog(
174
+ 'Unexpected response from FB for tip info.',
175
+ array('tip_info' => $tip_info),
176
+ true);
177
+ return;
178
+ }
179
+ update_option('fb_info_banner_last_best_tip',
180
+ is_object($tip_info) || is_array($tip_info)
181
+ ? json_encode($tip_info) : $tip_info);
182
+ }
183
+
184
+ $dismiss_url = $this->dismiss_url();
185
+ echo '<div class="updated fade"><div id="fbinfobanner"><div><img src="'. $tip_img_url .
186
+ '" class="iconDetails"></div><p class = "tipTitle">' .
187
+ __('<strong>' . $tip_title . '</strong>', 'facebook-for-woocommerce') . "\n";
188
+ echo '<p class = "tipContent">'.
189
+ __($tip_body, 'facebook-for-woocommerce') . '</p>';
190
+ echo '<p class = "tipButton"><a href="' . $tip_action_link . '" class = "btn" onclick="fb_woo_infobanner_post_click(); return true;" title="' .
191
+ __('Click and redirect.', 'facebook-for-woocommerce').
192
+ '"> ' . __($tip_action, 'facebook-for-woocommerce') . '</a>' .
193
+ '<a href="' . esc_url($dismiss_url). '" class = "btn dismiss grey" onclick="fb_woo_infobanner_post_xout(); return true;" title="' .
194
+ __('Dismiss this notice.', 'facebook-for-woocommerce').
195
+ '"> ' . __('Dismiss', 'facebook-for-woocommerce') . '</a></p></div></div>';
196
+ }
197
+
198
+ /**
199
+ * Returns the url that the user clicks to remove the info banner
200
+ * @return (string)
201
+ */
202
+ private function dismiss_url() {
203
+ $url = admin_url('admin.php');
204
+
205
+ $url = add_query_arg(array(
206
+ 'page' => 'wc-settings',
207
+ 'tab' => 'integration',
208
+ 'wc-notice' => 'dismiss-fb-info-banner',
209
+ ), $url);
210
+
211
+ return wp_nonce_url($url, 'woocommerce_info_banner_dismiss');
212
+ }
213
+
214
+ /**
215
+ * Handles the dismiss action so that the banner can be permanently hidden
216
+ * during time threshold
217
+ */
218
+ public function dismiss_banner() {
219
+ if (!isset($_GET['wc-notice'])) {
220
+ return;
221
+ }
222
+
223
+ if ('dismiss-fb-info-banner' !== $_GET['wc-notice']) {
224
+ return;
225
+ }
226
+
227
+ if (!check_admin_referer('woocommerce_info_banner_dismiss')) {
228
+ return;
229
+ }
230
+
231
+ // Delete cached tip if xout.
232
+ delete_option('fb_info_banner_last_best_tip');
233
+ if (wp_get_referer()) {
234
+ wp_safe_redirect(wp_get_referer());
235
+ } else {
236
+ wp_safe_redirect(admin_url('admin.php?page=wc-settings&tab=integration'));
237
+ }
238
+ }
239
+ }
240
+
241
+ endif;
includes/fbproduct.php ADDED
@@ -0,0 +1,752 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (! defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (!class_exists('WC_Facebookcommerce_Utils')) {
16
+ include_once 'includes/fbutils.php';
17
+ }
18
+
19
+ if (! class_exists('WC_Facebook_Product')) :
20
+
21
+ /**
22
+ * Custom FB Product proxy class
23
+ */
24
+ class WC_Facebook_Product {
25
+
26
+ // Should match facebook-commerce.php while we migrate that code over
27
+ // to this object.
28
+ const FB_PRODUCT_DESCRIPTION = 'fb_product_description';
29
+ const FB_PRODUCT_PRICE = 'fb_product_price';
30
+ const FB_PRODUCT_IMAGE = 'fb_product_image';
31
+ const FB_VARIANT_IMAGE = 'fb_image';
32
+ const FB_VISIBILITY = 'fb_visibility';
33
+
34
+ const MIN_DATE_1 = '1970-01-29';
35
+ const MIN_DATE_2 = '1970-01-30';
36
+ const MAX_DATE = '2038-01-17';
37
+ const MAX_TIME = 'T23:59+00:00';
38
+ const MIN_TIME = 'T00:00+00:00';
39
+
40
+ static $use_checkout_url = array('simple' => 1, 'variable' => 1, 'variation' => 1);
41
+
42
+ public function __construct(
43
+ $wpid, $parent_product = null) {
44
+ $this->id = $wpid;
45
+ $this->fb_description = '';
46
+ $this->fb_visibility = get_post_meta($wpid, self::FB_VISIBILITY, true);
47
+ $this->woo_product = wc_get_product($wpid);
48
+ $this->gallery_urls = null;
49
+ $this->fb_use_parent_image = null;
50
+ $this->fb_price = 0;
51
+ $this->main_description = '';
52
+ $this->sync_short_description = get_option('fb_sync_short_description', false);
53
+
54
+ // Variable products should use some data from the parent_product
55
+ // For performance reasons, that data shouldn't be regenerated every time.
56
+ if ($parent_product) {
57
+ $this->gallery_urls = $parent_product->get_gallery_urls();
58
+ $this->fb_use_parent_image = $parent_product->get_use_parent_image();
59
+ $this->main_description = WC_Facebookcommerce_Utils::clean_string(
60
+ $parent_product->get_description());
61
+ }
62
+ }
63
+
64
+ public function exists() {
65
+ return ($this->woo_product !== null && $this->woo_product !== false);
66
+ }
67
+
68
+ // Fall back to calling method on $woo_product
69
+ public function __call($function, $args) {
70
+ if ($this->woo_product) {
71
+ return call_user_func_array(array($this->woo_product, $function), $args);
72
+ } else {
73
+ $e = new Exception();
74
+ $backtrace = var_export($e->getTraceAsString(), true);
75
+ WC_Facebookcommerce_Utils::fblog(
76
+ "Calling $function on Null Woo Object. Trace:\n".$backtrace,
77
+ array(),
78
+ true);
79
+ return null;
80
+ }
81
+ }
82
+
83
+ public function get_gallery_urls() {
84
+ if ($this->gallery_urls === null) {
85
+ if (is_callable(array($this, 'get_gallery_image_ids'))) {
86
+ $image_ids = $this->get_gallery_image_ids();
87
+ } else {
88
+ $image_ids = $this->get_gallery_attachment_ids();
89
+ }
90
+ $gallery_urls = array();
91
+ foreach ($image_ids as $image_id) {
92
+ $image_url = wp_get_attachment_url($image_id);
93
+ if (!empty($image_url)) {
94
+ array_push($gallery_urls,
95
+ WC_Facebookcommerce_Utils::make_url($image_url));
96
+ }
97
+ }
98
+ $this->gallery_urls = array_filter($gallery_urls);
99
+ }
100
+
101
+ return $this->gallery_urls;
102
+ }
103
+
104
+ public function get_post_data() {
105
+ if (is_callable('get_post')) {
106
+ return get_post($this->id);
107
+ } else {
108
+ return $this->get_post_data();
109
+ }
110
+ }
111
+
112
+ public function get_fb_price() {
113
+ // Cache the price in this object in case of multiple calls.
114
+ if ($this->fb_price) {
115
+ return $this->fb_price;
116
+ }
117
+
118
+ $price = get_post_meta(
119
+ $this->id,
120
+ self::FB_PRODUCT_PRICE,
121
+ true);
122
+
123
+ if (is_numeric($price)) {
124
+ return intval(round($price * 100));
125
+ }
126
+
127
+ // If product is composite product, we rely on their pricing.
128
+ if (class_exists('WC_Product_Composite')
129
+ && $this->woo_product->get_type() === 'composite') {
130
+ $price = get_option('woocommerce_tax_display_shop') === 'incl'
131
+ ? $this->woo_product->get_composite_price_including_tax()
132
+ : $this->woo_product->get_composite_price();
133
+ $this->fb_price = intval(round($price * 100));
134
+ return $this->fb_price;
135
+ }
136
+
137
+ // Get regular price: regular price doesn't include sales
138
+ $regular_price = floatval($this->get_regular_price());
139
+
140
+ // If it's a bookable product, the normal price is null/0.
141
+ if (!$regular_price &&
142
+ class_exists('WC_Product_Booking') &&
143
+ is_wc_booking_product($this)) {
144
+ $product = new WC_Product_Booking($this->woo_product);
145
+ $regular_price = $product->get_display_cost();
146
+ }
147
+
148
+ // Get regular price plus tax, if it's set to display and taxable
149
+ // whether price includes tax is based on 'woocommerce_tax_display_shop'
150
+ $price = $this->get_price_plus_tax($regular_price);
151
+ $this->fb_price = intval(round($price * 100));
152
+ return $this->fb_price;
153
+ }
154
+
155
+ public function get_all_image_urls() {
156
+ $image_urls = array();
157
+ $parent_image_id = $this->get_parent_image_id();
158
+ $image_url = wp_get_attachment_url(
159
+ ($parent_image_id) ?: $this->woo_product->get_image_id());
160
+
161
+ if ($image_url) {
162
+ $image_url = WC_Facebookcommerce_Utils::make_url($image_url);
163
+ array_push($image_urls, $image_url);
164
+ }
165
+
166
+ // For variable products, add the variation specific image.
167
+ if ($parent_image_id) {
168
+ $image_url2 = wp_get_attachment_url($this->woo_product->get_image_id());
169
+ $image_url2 = WC_Facebookcommerce_Utils::make_url($image_url2);
170
+ if ($image_url != $image_url2) {
171
+ // A Checkbox toggles which image is primary.
172
+ // Default to variant specific image as primary.
173
+ if ($this->fb_use_parent_image) {
174
+ array_push($image_urls, $image_url2);
175
+ } else {
176
+ array_unshift($image_urls, $image_url2);
177
+ }
178
+ }
179
+ }
180
+
181
+ $gallery_urls = $this->get_gallery_urls();
182
+ $image_urls = array_merge($image_urls, $gallery_urls);
183
+ $image_urls = array_filter($image_urls);
184
+
185
+ // If there are no images, create a placeholder image.
186
+ if (empty($image_urls)) {
187
+ $name = urlencode(strip_tags($this->woo_product->get_title()));
188
+ $image_url = 'https://placeholdit.imgix.net/~text?txtsize=33&txt='
189
+ . $name . '&w=530&h=530'; // TODO: BETTER PLACEHOLDER
190
+ return array($image_url);
191
+ }
192
+
193
+ $image_override = get_post_meta($this->id, self::FB_PRODUCT_IMAGE, true);
194
+ if ($image_override) {
195
+ array_unshift($image_urls, $image_override);
196
+ $image_urls = array_unique($image_urls);
197
+ }
198
+
199
+ return $image_urls;
200
+ }
201
+
202
+ // Returns the parent image id for variable products only.
203
+ public function get_parent_image_id() {
204
+ if (WC_Facebookcommerce_Utils::is_variation_type($this->woo_product->get_type())) {
205
+ $parent_data = $this->get_parent_data();
206
+ return $parent_data['image_id'];
207
+ }
208
+ return null;
209
+ }
210
+
211
+ public function set_description($description) {
212
+ $description = stripslashes(
213
+ WC_Facebookcommerce_Utils::clean_string($description));
214
+ $this->fb_description = $description;
215
+ update_post_meta(
216
+ $this->id,
217
+ self::FB_PRODUCT_DESCRIPTION,
218
+ $description);
219
+ }
220
+
221
+ public function set_product_image($image) {
222
+ if ($image !== null && strlen($image) !== 0) {
223
+ $image = WC_Facebookcommerce_Utils::clean_string($image);
224
+ $image = WC_Facebookcommerce_Utils::make_url($image);
225
+ update_post_meta(
226
+ $this->id,
227
+ self::FB_PRODUCT_IMAGE,
228
+ $image);
229
+ }
230
+ }
231
+
232
+ public function set_price($price) {
233
+ if (is_numeric($price)) {
234
+ $this->fb_price = intval(round($price * 100));
235
+ update_post_meta(
236
+ $this->id,
237
+ self::FB_PRODUCT_PRICE,
238
+ $price);
239
+ } else {
240
+ delete_post_meta(
241
+ $this->id,
242
+ self::FB_PRODUCT_PRICE);
243
+ }
244
+ }
245
+
246
+ public function get_use_parent_image() {
247
+ if ($this->fb_use_parent_image === null) {
248
+ $variant_image_setting =
249
+ get_post_meta($this->id, self::FB_VARIANT_IMAGE, true);
250
+ $this->fb_use_parent_image = ($variant_image_setting) ? true : false;
251
+ }
252
+ return $this->fb_use_parent_image;
253
+ }
254
+
255
+ public function set_use_parent_image($setting) {
256
+ $this->fb_use_parent_image = ($setting == 'yes');
257
+ update_post_meta(
258
+ $this->id,
259
+ self::FB_VARIANT_IMAGE,
260
+ $this->fb_use_parent_image);
261
+ }
262
+
263
+ public function get_fb_description() {
264
+ if ($this->fb_description) {
265
+ return $this->fb_description;
266
+ }
267
+
268
+ $description = get_post_meta(
269
+ $this->id,
270
+ self::FB_PRODUCT_DESCRIPTION,
271
+ true);
272
+
273
+ if ($description) {
274
+ return $description;
275
+ }
276
+
277
+ if (WC_Facebookcommerce_Utils::is_variation_type($this->woo_product->get_type())) {
278
+ $description = WC_Facebookcommerce_Utils::clean_string(
279
+ $this->get_description());
280
+ if ($description) {
281
+ return $description;
282
+ }
283
+ if ($this->main_description) {
284
+ return $this->main_description;
285
+ }
286
+ }
287
+
288
+ $post = $this->get_post_data();
289
+
290
+ $post_content = WC_Facebookcommerce_Utils::clean_string(
291
+ $post->post_content);
292
+ $post_excerpt = WC_Facebookcommerce_Utils::clean_string(
293
+ $post->post_excerpt);
294
+ $post_title = WC_Facebookcommerce_Utils::clean_string(
295
+ $post->post_title);
296
+
297
+ // Sanitize description
298
+ if ($post_content) {
299
+ $description = $post_content;
300
+ }
301
+ if ($this->sync_short_description || ($description == '' && $post_excerpt)) {
302
+ $description = $post_excerpt;
303
+ }
304
+ if ($description == '') {
305
+ $description = $post_title;
306
+ }
307
+
308
+ return $description;
309
+ }
310
+
311
+ public function add_sale_price($product_data) {
312
+ // initialize sale date and sale_price
313
+ $product_data['sale_price_start_date'] = self::MIN_DATE_1 . self::MIN_TIME;
314
+ $product_data['sale_price_end_date'] = self::MIN_DATE_2 . self::MAX_TIME;
315
+ $product_data['sale_price'] = $product_data['price'];
316
+
317
+ $sale_price = $this->woo_product->get_sale_price();
318
+ // check if sale exist
319
+ if (!is_numeric($sale_price)) {
320
+ return $product_data;
321
+ }
322
+ $sale_price =
323
+ intval(round($this->get_price_plus_tax($sale_price) * 100));
324
+
325
+ $sale_start =
326
+ ($date = get_post_meta($this->id, '_sale_price_dates_from', true))
327
+ ? date_i18n('Y-m-d', $date) . self::MIN_TIME
328
+ : self::MIN_DATE_1 . self::MIN_TIME;
329
+
330
+ $sale_end =
331
+ ($date = get_post_meta($this->id, '_sale_price_dates_to', true))
332
+ ? date_i18n('Y-m-d', $date) . self::MAX_TIME
333
+ : self::MAX_DATE . self::MAX_TIME;
334
+
335
+ // check if sale is expired and sale time range is valid
336
+ $product_data['sale_price_start_date'] = $sale_start;
337
+ $product_data['sale_price_end_date'] = $sale_end;
338
+ $product_data['sale_price'] = $sale_price;
339
+ return $product_data;
340
+ }
341
+
342
+ public function is_hidden() {
343
+ $wpid = $this->id;
344
+ if (WC_Facebookcommerce_Utils::is_variation_type($this->get_type())) {
345
+ $wpid = $this->get_parent_id();
346
+ }
347
+ $hidden_from_catalog = has_term(
348
+ 'exclude-from-catalog',
349
+ 'product_visibility',
350
+ $wpid);
351
+ $hidden_from_search = has_term(
352
+ 'exclude-from-search',
353
+ 'product_visibility',
354
+ $wpid);
355
+ // fb_visibility === '': after initial sync by feed
356
+ // fb_visibility === false: set hidden on FB metadata
357
+ // Explicitly check whether flip 'hide' before.
358
+ return ($hidden_from_catalog && $hidden_from_search) ||
359
+ $this->fb_visibility === false || !$this->get_fb_price();
360
+ }
361
+
362
+ public function get_price_plus_tax($price) {
363
+ $woo_product = $this->woo_product;
364
+ // // wc_get_price_including_tax exist for Woo > 2.7
365
+ if (function_exists('wc_get_price_including_tax')) {
366
+ $args = array( 'qty' => 1, 'price' => $price);
367
+ return get_option('woocommerce_tax_display_shop') === 'incl'
368
+ ? wc_get_price_including_tax($woo_product, $args)
369
+ : wc_get_price_excluding_tax($woo_product, $args);
370
+ } else {
371
+ return get_option('woocommerce_tax_display_shop') === 'incl'
372
+ ? $woo_product->get_price_including_tax(1, $price)
373
+ : $woo_product->get_price_excluding_tax(1, $price);
374
+ }
375
+ }
376
+
377
+ public function get_grouped_product_option_names($key, $option_values) {
378
+ // Convert all slug_names in $option_values into the visible names that
379
+ // advertisers have set to be the display names for a given attribute value
380
+ $terms = get_the_terms($this->id, $key);
381
+ return array_map(
382
+ function ($slug_name) use ($terms) {
383
+ foreach ($terms as $term) {
384
+ if ($term->slug === $slug_name) {
385
+ return $term->name;
386
+ }
387
+ }
388
+ return $slug_name;
389
+ },
390
+ $option_values);
391
+ }
392
+
393
+ public function get_variant_option_name($label, $default_value) {
394
+ // For the given label, get the Visible name rather than the slug
395
+ $meta = get_post_meta($this->id, $label, true);
396
+ $attribute_name = str_replace('attribute_', '', $label);
397
+ $term = get_term_by('slug', $meta, $attribute_name);
398
+ return $term && $term->name ? $term->name : $default_value;
399
+ }
400
+
401
+ public function update_visibility($is_product_page, $visible_box_checked) {
402
+ $visibility = get_post_meta($this->id, self::FB_VISIBILITY, true);
403
+ if ($visibility && !$is_product_page) {
404
+ // If the product was previously set to visible, keep it as visible
405
+ // (unless we're on the product page)
406
+ $this->fb_visibility = $visibility;
407
+ } else {
408
+ // If the product is not visible OR we're on the product page,
409
+ // then update the visibility as needed.
410
+ $this->fb_visibility = $visible_box_checked ? true : false;
411
+ update_post_meta($this->id, self::FB_VISIBILITY, $this->fb_visibility);
412
+ }
413
+ }
414
+
415
+ // wrapper function to find item_id for default variation
416
+ function find_matching_product_variation() {
417
+ if (is_callable(array($this, 'get_default_attributes'))) {
418
+ $default_attributes = $this->get_default_attributes();
419
+ } else {
420
+ $default_attributes = $this->get_variation_default_attributes();
421
+ }
422
+
423
+ if (!$default_attributes) {
424
+ return;
425
+ }
426
+ foreach ($default_attributes as $key => $value) {
427
+ if (strncmp($key, 'attribute_', strlen('attribute_')) === 0) {
428
+ continue;
429
+ }
430
+ unset($default_attributes[$key]);
431
+ $default_attributes[sprintf('attribute_%s', $key)] = $value;
432
+ }
433
+ if (class_exists('WC_Data_Store')) {
434
+ // for >= woo 3.0.0
435
+ $data_store = WC_Data_Store::load('product');
436
+ return
437
+ $data_store->find_matching_product_variation(
438
+ $this,
439
+ $default_attributes);
440
+ } else {
441
+ return $this->get_matching_variation($default_attributes);
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Assemble product payload for POST
447
+ **/
448
+ function prepare_product(
449
+ $retailer_id = null,
450
+ $prepare_for_product_feed = false) {
451
+ if (!$retailer_id) {
452
+ $retailer_id =
453
+ WC_Facebookcommerce_Utils::get_fb_retailer_id($this);
454
+ }
455
+ $image_urls = $this->get_all_image_urls();
456
+
457
+ // Replace Wordpress sanitization's ampersand with a real ampersand.
458
+ $product_url = str_replace(
459
+ '&amp%3B',
460
+ '&',
461
+ html_entity_decode($this->get_permalink()));
462
+
463
+ // Use product_url for external/bundle product setting.
464
+ $product_type = $this->get_type();
465
+ if (!$product_type || !isset(self::$use_checkout_url[$product_type])) {
466
+ $checkout_url = $product_url;
467
+ } else if (wc_get_cart_url()) {
468
+ $char = '?';
469
+ // Some merchant cart pages are actually a querystring
470
+ if (strpos(wc_get_cart_url(), '?') !== false) {
471
+ $char = '&';
472
+ }
473
+
474
+ $checkout_url = WC_Facebookcommerce_Utils::make_url(
475
+ wc_get_cart_url() . $char);
476
+
477
+ if (WC_Facebookcommerce_Utils::is_variation_type($this->get_type())) {
478
+ $query_data = array(
479
+ 'add-to-cart' => $this->get_parent_id(),
480
+ 'variation_id' => $this->get_id()
481
+ );
482
+
483
+ $query_data = array_merge(
484
+ $query_data,
485
+ $this->get_variation_attributes());
486
+
487
+ } else {
488
+ $query_data = array(
489
+ 'add-to-cart' => $this->get_id()
490
+ );
491
+ }
492
+
493
+ $checkout_url = $checkout_url . http_build_query($query_data);
494
+
495
+ } else {
496
+ $checkout_url = null;
497
+ }
498
+
499
+ $id = $this->get_id();
500
+ if (WC_Facebookcommerce_Utils::is_variation_type($this->get_type())) {
501
+ $id = $this->get_parent_id();
502
+ }
503
+ $categories =
504
+ WC_Facebookcommerce_Utils::get_product_categories($id);
505
+ $brand = get_the_term_list($id, 'product_brand', '', ', ');
506
+ $brand = is_wp_error($brand) || !$brand
507
+ ? WC_Facebookcommerce_Utils::get_store_name()
508
+ : WC_Facebookcommerce_Utils::clean_string($brand);
509
+
510
+ $product_data = array(
511
+ 'name' => WC_Facebookcommerce_Utils::clean_string(
512
+ $this->get_title()),
513
+ 'description' => $this->get_fb_description(),
514
+ 'image_url' => $image_urls[0], // The array can't be empty.
515
+ 'additional_image_urls' => array_filter($image_urls),
516
+ 'url'=> $product_url,
517
+ 'category' => $categories['categories'],
518
+ 'brand' => $brand,
519
+ 'retailer_id' => $retailer_id,
520
+ 'price' => $this->get_fb_price(),
521
+ 'currency' => get_woocommerce_currency(),
522
+ 'availability' => $this->is_in_stock() ? 'in stock' :
523
+ 'out of stock',
524
+ 'visibility' => !$this->is_hidden()
525
+ ? 'published'
526
+ : 'staging'
527
+ );
528
+
529
+ // Only use checkout URLs if they exist.
530
+ if ($checkout_url) {
531
+ $product_data['checkout_url'] = $checkout_url;
532
+ }
533
+
534
+ $product_data = $this->add_sale_price($product_data);
535
+
536
+ // IF using WPML, set the product to staging unless it is in the
537
+ // default language. WPML >= 3.2 Supported.
538
+ if (defined('ICL_LANGUAGE_CODE')) {
539
+ if (class_exists('WC_Facebook_WPML_Injector') && WC_Facebook_WPML_Injector::should_hide($id)) {
540
+ $product_data['visibility'] = 'staging';
541
+ }
542
+ }
543
+
544
+ // Exclude variations that are "virtual" products from export to Facebook &&
545
+ // No Visibility Option for Variations
546
+ if (true === $this->get_virtual()) {
547
+ $product_data['visibility'] = 'staging';
548
+ }
549
+
550
+ if (!$prepare_for_product_feed) {
551
+ $this->prepare_variants_for_item($product_data);
552
+ } else if (
553
+ WC_Facebookcommerce_Utils::is_all_caps($product_data['description'])
554
+ ) {
555
+ $product_data['description'] =
556
+ mb_strtolower($product_data['description']);
557
+ }
558
+
559
+ /**
560
+ * Filters the generated product data.
561
+ *
562
+ * @param int $id Woocommerce product id
563
+ * @param array $product_data An array of product data
564
+ */
565
+ return apply_filters(
566
+ "facebook_for_woocommerce_integration_prepare_product",
567
+ $product_data,
568
+ $id);
569
+ }
570
+
571
+
572
+ /**
573
+ * Modify Woo variant/taxonomies to be FB compatible
574
+ **/
575
+ public function prepare_variants_for_item(&$product_data) {
576
+ if (!WC_Facebookcommerce_Utils::is_variation_type(
577
+ $this->get_type())) {
578
+ return;
579
+ }
580
+
581
+ $attributes = $this->get_variation_attributes();
582
+ if (!$attributes) {
583
+ return;
584
+ }
585
+
586
+ $variant_names = array_keys($attributes);
587
+ $variant_array = array();
588
+
589
+ // Loop through variants (size, color, etc) if they exist
590
+ // For each product field type, pull the single variant
591
+ foreach ($variant_names as $orig_name) {
592
+ // Retrieve label name for attribute
593
+ $label = wc_attribute_label($orig_name, $this);
594
+
595
+ // Clean up variant name (e.g. pa_color should be color)
596
+ // Replace "custom_data:foo" with just "foo" so we can use the key
597
+ // Product item API expects "custom_data" instead of "custom_data:foo"
598
+ $new_name = str_replace(
599
+ 'custom_data:',
600
+ '',
601
+ WC_Facebookcommerce_Utils::sanitize_variant_name($orig_name));
602
+
603
+ // Sometimes WC returns an array, sometimes it's an assoc array, depending
604
+ // on what type of taxonomy it's using. array_values will guarantee we
605
+ // only get a flat array of values.
606
+ $options = $this->get_variant_option_name(
607
+ $label,
608
+ $attributes[$orig_name]);
609
+ if (isset($options)) {
610
+ if (is_array($options)) {
611
+ $option_values = array_values($options);
612
+ } else {
613
+ $option_values = array($options);
614
+ // If this attribute has value 'any', options will be empty strings
615
+ // Redirect to product page to select variants.
616
+ // Reset checkout url since checkout_url (build from query data will
617
+ // be invalid in this case.
618
+ if (count($option_values) === 1 && empty($option_values[0])) {
619
+ $option_values[0] = 'any';
620
+ $product_data['checkout_url'] = $product_data['url'];
621
+ }
622
+ }
623
+ if ($new_name === WC_Facebookcommerce_Utils::FB_VARIANT_GENDER) {
624
+ // If we can't validate the gender, this will be null.
625
+ $product_data[$new_name] =
626
+ WC_Facebookcommerce_Utils::validateGender($option_values[0]);
627
+ }
628
+
629
+ switch ($new_name) {
630
+ case WC_Facebookcommerce_Utils::FB_VARIANT_SIZE:
631
+ case WC_Facebookcommerce_Utils::FB_VARIANT_COLOR:
632
+ case WC_Facebookcommerce_Utils::FB_VARIANT_PATTERN:
633
+ array_push($variant_array, array(
634
+ 'product_field' => $new_name,
635
+ 'label' => $label,
636
+ 'options' => $option_values,
637
+ ));
638
+ $product_data[$new_name] = $option_values[0];
639
+ break;
640
+ case WC_Facebookcommerce_Utils::FB_VARIANT_GENDER:
641
+ // If we can't validate the GENDER field, we'll fall through to the
642
+ // default case and set the gender into custom data.
643
+ if ($product_data[$new_name]) {
644
+ array_push($variant_array, array(
645
+ 'product_field' => $new_name,
646
+ 'label' => $label,
647
+ 'options' => $option_values,
648
+ ));
649
+ break;
650
+ }
651
+
652
+ default:
653
+ // This is for any custom_data.
654
+ if (!isset($product_data['custom_data'])) {
655
+ $product_data['custom_data'] = array();
656
+ }
657
+ $product_data['custom_data'][$new_name]
658
+ = urldecode($option_values[0]);
659
+ break;
660
+ }
661
+ } else {
662
+ WC_Facebookcommerce_Utils::log(
663
+ $this->get_id() . ": No options for " . $orig_name);
664
+ continue;
665
+ }
666
+ }
667
+ return $variant_array;
668
+ }
669
+
670
+ /**
671
+ * Modify Woo variant/taxonomies for variable products to be FB compatible
672
+ **/
673
+ public function prepare_variants_for_group($feed_data = false) {
674
+ if (!WC_Facebookcommerce_Utils::is_variable_type(
675
+ $this->get_type())) {
676
+ WC_Facebookcommerce_Utils::fblog(
677
+ "prepare_variants_for_group called on non-variable product");
678
+ return;
679
+ }
680
+
681
+ $variation_attributes = $this->get_variation_attributes();
682
+ if (!$variation_attributes) {
683
+ return;
684
+ }
685
+ $final_variants = array();
686
+
687
+ $attrs = array_keys($this->get_attributes());
688
+ foreach ($attrs as $name) {
689
+ $label = wc_attribute_label($name, $this);
690
+
691
+ if (taxonomy_is_product_attribute($name)) {
692
+ $key = $name;
693
+ } else {
694
+ // variation_attributes keys are labels for custom attrs for some reason
695
+ $key = $label;
696
+ }
697
+
698
+ if (!$key) {
699
+ WC_Facebookcommerce_Utils::fblog(
700
+ "Critical error: can't get attribute name or label!");
701
+ return;
702
+ }
703
+
704
+ if (isset($variation_attributes[$key])) {
705
+ // Array of the options (e.g. small, medium, large)
706
+ $option_values = $variation_attributes[$key];
707
+ } else {
708
+ WC_Facebookcommerce_Utils::log(
709
+ $this->get_id() . ": No options for " . $name);
710
+ continue; // Skip variations without valid attribute options
711
+ }
712
+
713
+ // If this is a wc_product_variable, check default attrib.
714
+ // If it's being used, show it as the first option on Facebook.
715
+ $first_option = $this->get_variation_default_attribute($key);
716
+ if ($first_option) {
717
+ $idx = array_search($first_option, $option_values);
718
+ unset($option_values[$idx]);
719
+ array_unshift($option_values, $first_option);
720
+ }
721
+
722
+ if (
723
+ function_exists('taxonomy_is_product_attribute') &&
724
+ taxonomy_is_product_attribute($name)
725
+ ) {
726
+ $option_values = $this->get_grouped_product_option_names(
727
+ $key,
728
+ $option_values);
729
+ }
730
+
731
+ // https://developers.facebook.com/docs/marketing-api/reference/product-variant/
732
+ // For API approach, product_field need to start with 'custom_data:'
733
+ // Clean up variant name (e.g. pa_color should be color)
734
+ $name = WC_Facebookcommerce_Utils::sanitize_variant_name($name);
735
+
736
+ // For feed uploading, product field should remove prefix 'custom_data:'
737
+ if ($feed_data) {
738
+ $name = str_replace('custom_data:', '', $name);
739
+ }
740
+ array_push($final_variants, array(
741
+ 'product_field' => $name,
742
+ 'label' => $label,
743
+ 'options' => $option_values,
744
+ ));
745
+ }
746
+
747
+ return $final_variants;
748
+
749
+ }
750
+ }
751
+
752
+ endif;
includes/fbproductfeed.php ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (! defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (! class_exists('WC_Facebook_Product_Feed')) :
16
+
17
+ /**
18
+ * Initial Sync by Facebook feed class
19
+ */
20
+ class WC_Facebook_Product_Feed {
21
+
22
+ const FACEBOOK_CATALOG_FEED_FILENAME = 'fae_product_catalog.csv';
23
+ const FB_ADDITIONAL_IMAGES_FOR_FEED = 5;
24
+ const FEED_NAME = 'Initial product sync from WooCommerce. DO NOT DELETE.';
25
+ const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
26
+ const FB_VISIBILITY = 'fb_visibility';
27
+
28
+ private $has_default_product_count = 0;
29
+ private $no_default_product_count = 0;
30
+
31
+ public function __construct(
32
+ $facebook_catalog_id,
33
+ $fbgraph,
34
+ $feed_id = null) {
35
+ $this->facebook_catalog_id = $facebook_catalog_id;
36
+ $this->fbgraph = $fbgraph;
37
+ $this->feed_id = $feed_id;
38
+ }
39
+
40
+ public function sync_all_products_using_feed() {
41
+ $start_time = microtime(true);
42
+ $this->log_feed_progress('Sync all products using feed');
43
+
44
+ if (!is_writable(dirname(__FILE__))) {
45
+ $this->log_feed_progress(
46
+ 'Failure - Sync all products using feed, folder is not writable');
47
+ return false;
48
+ }
49
+
50
+ if (!$this->generate_productfeed_file()) {
51
+ $this->log_feed_progress(
52
+ 'Failure - Sync all products using feed, feed file not generated');
53
+ return false;
54
+ }
55
+ $this->log_feed_progress('Sync all products using feed, feed file generated');
56
+
57
+ if (!$this->feed_id) {
58
+ $this->feed_id = $this->create_feed();
59
+ if (!$this->feed_id) {
60
+ $this->log_feed_progress(
61
+ 'Failure - Sync all products using feed, facebook feed not created');
62
+ return false;
63
+ }
64
+ $this->log_feed_progress(
65
+ 'Sync all products using feed, facebook feed created');
66
+ } else {
67
+ $this->log_feed_progress(
68
+ 'Sync all products using feed, facebook feed already exists.');
69
+ }
70
+
71
+
72
+ $this->upload_id = $this->create_upload($this->feed_id);
73
+ if (!$this->upload_id) {
74
+ $this->log_feed_progress(
75
+ 'Failure - Sync all products using feed, facebook upload not created');
76
+ return false;
77
+ }
78
+ $this->log_feed_progress(
79
+ 'Sync all products using feed, facebook upload created');
80
+
81
+ unlink(dirname(__FILE__) .
82
+ DIRECTORY_SEPARATOR . (self::FACEBOOK_CATALOG_FEED_FILENAME));
83
+
84
+ $total_product_count =
85
+ $this->has_default_product_count + $this->no_default_product_count;
86
+ $default_product_percentage =
87
+ ($total_product_count == 0 || $this->has_default_product_count == 0)
88
+ ? 0
89
+ : $this->has_default_product_count / $total_product_count * 100;
90
+ $time_spent = microtime(true) - $start_time;
91
+ $data = array();
92
+ // Only log performance if this store has products in order to get average
93
+ // performance.
94
+ if ($total_product_count != 0) {
95
+ $data = array(
96
+ 'sync_time' => $time_spent,
97
+ 'total' => $total_product_count,
98
+ 'default_product_percentage' => $default_product_percentage,
99
+ );
100
+ }
101
+ $this->log_feed_progress('Complete - Sync all products using feed.', $data);
102
+ return true;
103
+ }
104
+
105
+ public function generate_productfeed_file() {
106
+ $this->log_feed_progress('Generating product feed file');
107
+ $post_ids = $this->get_product_wpid();
108
+ $all_parent_product = array_map(function($post_id) {
109
+ if (get_post_type($post_id) == 'product_variation') {
110
+ return wp_get_post_parent_id($post_id);
111
+ }
112
+ }, $post_ids);
113
+ $all_parent_product = array_filter(array_unique($all_parent_product));
114
+ $product_ids = array_diff($post_ids, $all_parent_product);
115
+ return $this->write_product_feed_file($product_ids);
116
+ }
117
+
118
+ public function write_product_feed_file($wp_ids) {
119
+ try {
120
+ $feed_file =
121
+ fopen(dirname(__FILE__) . DIRECTORY_SEPARATOR .
122
+ (self::FACEBOOK_CATALOG_FEED_FILENAME), "w");
123
+ fwrite($feed_file, $this->get_product_feed_header_row());
124
+
125
+ $product_group_attribute_variants = array();
126
+ foreach ($wp_ids as $wp_id) {
127
+ $woo_product = new WC_Facebook_Product($wp_id);
128
+ if ($woo_product->is_hidden()) {
129
+ continue;
130
+ }
131
+ if (get_option('woocommerce_hide_out_of_stock_items') === 'yes' &&
132
+ !$woo_product->is_in_stock()) {
133
+ continue;
134
+ }
135
+ $product_data_as_feed_row = $this->prepare_product_for_feed(
136
+ $woo_product, $product_group_attribute_variants);
137
+ fwrite($feed_file, $product_data_as_feed_row);
138
+ }
139
+ fclose($feed_file);
140
+ wp_reset_postdata();
141
+ return true;
142
+ } catch (Exception $e) {
143
+ WC_Facebookcommerce_Utils::log(json_encode($e->getMessage()));
144
+ return false;
145
+ }
146
+ }
147
+
148
+ public function get_product_feed_header_row() {
149
+ return 'id,title,description,image_link,link,product_type,' .
150
+ 'brand,price,availability,item_group_id,checkout_url,' .
151
+ 'additional_image_link,sale_price_effective_date,sale_price,condition,' .
152
+ 'visibility,default_product,variant' . PHP_EOL;
153
+ }
154
+
155
+ /**
156
+ * Assemble product payload in feed upload for initial sync.
157
+ **/
158
+ private function prepare_product_for_feed(
159
+ $woo_product,
160
+ &$attribute_variants) {
161
+ $product_data = $woo_product->prepare_product(null, true);
162
+ $item_group_id = $product_data['retailer_id'];
163
+ // prepare variant column for variable products
164
+ $product_data['variant'] = '';
165
+ if (
166
+ WC_Facebookcommerce_Utils::is_variation_type($woo_product->get_type())
167
+ ) {
168
+ $parent_id = $woo_product->get_parent_id();
169
+
170
+ if (!isset($attribute_variants[$parent_id])) {
171
+ $parent_product = new WC_Facebook_Product($parent_id);
172
+
173
+ $gallery_urls = array_filter($parent_product->get_gallery_urls());
174
+ $variation_id = $parent_product->find_matching_product_variation();
175
+ $variants_for_group = $parent_product->prepare_variants_for_group(true);
176
+ $parent_attribute_values = array();
177
+ $parent_attribute_values['gallery_urls'] = $gallery_urls;
178
+ $parent_attribute_values['default_variant_id'] = $variation_id;
179
+ $parent_attribute_values['item_group_id'] =
180
+ WC_Facebookcommerce_Utils::get_fb_retailer_id($parent_product);
181
+ foreach ($variants_for_group as $variant) {
182
+ $parent_attribute_values[$variant['product_field']] =
183
+ $variant['options'];
184
+ }
185
+ // cache product group variants
186
+ $attribute_variants[$parent_id] = $parent_attribute_values;
187
+ }
188
+ $parent_attribute_values = $attribute_variants[$parent_id];
189
+ $variants_for_item =
190
+ $woo_product->prepare_variants_for_item($product_data);
191
+ $variant_feed_column = array();
192
+ foreach ($variants_for_item as $variant_array) {
193
+ static::format_variant_for_feed(
194
+ $variant_array['product_field'],
195
+ $variant_array['options'][0],
196
+ $parent_attribute_values,
197
+ $variant_feed_column);
198
+ }
199
+ if (isset($product_data['custom_data'])) {
200
+ foreach ($product_data['custom_data'] as $product_field => $value) {
201
+ static::format_variant_for_feed(
202
+ $product_field,
203
+ $value,
204
+ $parent_attribute_values,
205
+ $variant_feed_column);
206
+ }
207
+ }
208
+ if (!empty($variant_feed_column)) {
209
+ $product_data['variant'] =
210
+ "\"" . implode(',', $variant_feed_column) . "\"";
211
+ }
212
+ if (isset($parent_attribute_values['gallery_urls'])) {
213
+ $product_data['additional_image_urls'] =
214
+ array_merge($product_data['additional_image_urls'],
215
+ $parent_attribute_values['gallery_urls']);
216
+ }
217
+ if (isset($parent_attribute_values['item_group_id'])) {
218
+ $item_group_id = $parent_attribute_values['item_group_id'];
219
+ }
220
+
221
+ $product_data['default_product'] =
222
+ $parent_attribute_values['default_variant_id'] == $woo_product->id
223
+ ? 'default'
224
+ :'';
225
+
226
+ // If this group has default variant value, log this product item
227
+ if (isset($parent_attribute_values['default_variant_id']) &&
228
+ !empty($parent_attribute_values['default_variant_id'])) {
229
+ $this->has_default_product_count++;
230
+ } else {
231
+ $this->no_default_product_count++;
232
+ }
233
+ }
234
+
235
+ // log simple product
236
+ if (!isset($product_data['default_product'])) {
237
+ $this->no_default_product_count++;
238
+ $product_data['default_product'];
239
+ }
240
+
241
+ return
242
+ $product_data['retailer_id'] . ',' .
243
+ static::format_string_for_feed($product_data['name']) . ',' .
244
+ static::format_string_for_feed($product_data['description']) . ',' .
245
+ $product_data['image_url'] . ',' .
246
+ $product_data['url'] . ',' .
247
+ static::format_string_for_feed($product_data['category']) . ',' .
248
+ static::format_string_for_feed($product_data['brand']) . ',' .
249
+ static::format_price_for_feed(
250
+ $product_data['price'], $product_data['currency']) . ',' .
251
+ $product_data['availability'] . ',' .
252
+ $item_group_id . ',' .
253
+ $product_data['checkout_url'] . ',' .
254
+ static::format_additional_image_url(
255
+ $product_data['additional_image_urls']) . ',' .
256
+ $product_data['sale_price_start_date'] . '/' .
257
+ $product_data['sale_price_end_date']. ',' .
258
+ static::format_price_for_feed(
259
+ $product_data['sale_price'], $product_data['currency']) . ',' .
260
+ 'new' . ',' .
261
+ $product_data['visibility'] . ',' .
262
+ $product_data['default_product'] . ',' .
263
+ $product_data['variant'] . PHP_EOL;
264
+ }
265
+
266
+ private function create_feed() {
267
+ $result = $this->fbgraph->create_feed(
268
+ $this->facebook_catalog_id, array('name' => self::FEED_NAME));
269
+ if (is_wp_error($result) || !isset($result['body'])) {
270
+ $this->log_feed_progress(json_encode($result));
271
+ return null;
272
+ }
273
+ $decode_result = WC_Facebookcommerce_Utils::decode_json($result['body']);
274
+ $feed_id = $decode_result->id;
275
+ if (!$feed_id) {
276
+ $this->log_feed_progress(
277
+ 'Response from creating feed not return feed id!');
278
+ return null;
279
+ }
280
+ return $feed_id;
281
+ }
282
+
283
+ private function create_upload($facebook_feed_id) {
284
+ $result = $this->fbgraph->create_upload(
285
+ $facebook_feed_id, dirname(__FILE__) . DIRECTORY_SEPARATOR .
286
+ (self::FACEBOOK_CATALOG_FEED_FILENAME));
287
+ if (is_null($result) || !isset($result['id']) || !$result['id']) {
288
+ $this->log_feed_progress(json_encode($result));
289
+ return null;
290
+ }
291
+ $upload_id = $result['id'];
292
+ return $upload_id;
293
+ }
294
+
295
+ private static function format_additional_image_url($product_image_urls) {
296
+ // returns the top 10 additional image urls separated by a comma
297
+ // according to feed api rules
298
+ $product_image_urls = array_slice(
299
+ $product_image_urls,
300
+ 0,
301
+ self::FB_ADDITIONAL_IMAGES_FOR_FEED);
302
+ if ($product_image_urls) {
303
+ return "\"" . implode(',', $product_image_urls) . "\"";
304
+ } else {
305
+ return '';
306
+ }
307
+ }
308
+
309
+ private static function format_string_for_feed($text) {
310
+ if ((bool)$text) {
311
+ return "\"" . str_replace('"', "'", $text) . "\"";
312
+ } else {
313
+ return '';
314
+ }
315
+ }
316
+
317
+ private static function format_price_for_feed($value, $currency) {
318
+ return (string)(round($value / (float) 100, 2)) . $currency;
319
+ }
320
+
321
+ private static function format_variant_for_feed(
322
+ $product_field,
323
+ $value,
324
+ $parent_attribute_values,
325
+ &$variant_feed_column) {
326
+ if (!array_key_exists($product_field, $parent_attribute_values)) {
327
+ return;
328
+ }
329
+ array_push($variant_feed_column,
330
+ $product_field . ':' .
331
+ implode('/', $parent_attribute_values[$product_field]) . ':' .
332
+ $value);
333
+ }
334
+
335
+ public function is_upload_complete(&$settings) {
336
+ $result = $this->fbgraph->get_upload_status($settings['fb_upload_id']);
337
+ if (is_wp_error($result) || !isset($result['body'])) {
338
+ $this->log_feed_progress(json_encode($result));
339
+ return 'error';
340
+ }
341
+ $decode_result = WC_Facebookcommerce_Utils::decode_json($result['body'], true);
342
+ $end_time = $decode_result['end_time'];
343
+ if (isset($end_time)) {
344
+ $settings['upload_end_time'] = $end_time;
345
+ return 'complete';
346
+ } else {
347
+ return 'in progress';
348
+ }
349
+ }
350
+
351
+ // Log progress in local log file and FB.
352
+ public function log_feed_progress($msg, $object = array()) {
353
+ WC_Facebookcommerce_Utils::fblog($msg, $object);
354
+ $msg = empty($object) ? $msg : $msg . json_encode($object);
355
+ WC_Facebookcommerce_Utils::log($msg);
356
+ }
357
+
358
+ public function get_product_wpid() {
359
+ $post_ids = WC_Facebookcommerce_Utils::get_wp_posts(
360
+ null,
361
+ null,
362
+ array('product', 'product_variation'));
363
+ return $post_ids;
364
+ }
365
+ }
366
+
367
+ endif;
includes/fbutils.php ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (!class_exists('WC_Facebookcommerce_Utils')) :
16
+
17
+ /**
18
+ * FB Graph API helper functions
19
+ *
20
+ */
21
+ class WC_Facebookcommerce_Utils {
22
+
23
+ const FB_RETAILER_ID_PREFIX = 'wc_post_id_';
24
+ const PLUGIN_VERSION = '1.9.11'; // Change it in `facebook-for-*.php` also
25
+
26
+ const FB_VARIANT_IMAGE = 'fb_image';
27
+ const FB_VARIANT_SIZE = 'size';
28
+ const FB_VARIANT_COLOR = 'color';
29
+ const FB_VARIANT_COLOUR = 'colour';
30
+ const FB_VARIANT_PATTERN = 'pattern';
31
+ const FB_VARIANT_GENDER = 'gender';
32
+
33
+ public static $ems = null;
34
+ public static $fbgraph = null;
35
+ public static $store_name = null;
36
+
37
+ public static $validGenderArray =
38
+ array("male" => 1, "female" => 1, "unisex" => 1);
39
+ /**
40
+ * WooCommerce 2.1 support for wc_enqueue_js
41
+ *
42
+ * @since 1.2.1
43
+ *
44
+ * @access public
45
+ * @param string $code
46
+ * @return void
47
+ */
48
+ public static function wc_enqueue_js($code) {
49
+ global $wc_queued_js;
50
+
51
+ if (function_exists('wc_enqueue_js') && empty($wc_queued_js)) {
52
+ wc_enqueue_js($code);
53
+ } else {
54
+ $wc_queued_js = $code."\n".$wc_queued_js;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Validate URLs, make relative URLs absolute
60
+ *
61
+ * @access public
62
+ * @param string $url
63
+ * @return string
64
+ */
65
+ public static function make_url($url) {
66
+ if (
67
+ // The first check incorrectly fails for URLs with special chars.
68
+ !filter_var($url , FILTER_VALIDATE_URL) &&
69
+ substr($url, 0, 4) !== 'http'
70
+ ) {
71
+ return get_site_url() . $url ;
72
+ } else {
73
+ return $url;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Product ID for Dynamic Ads on Facebook can be SKU or wc_post_id_123
79
+ * This function should be used to get retailer_id based on a WC_Product
80
+ * from WooCommerce
81
+ *
82
+ * @access public
83
+ * @param WC_Product $woo_product
84
+ * @return string
85
+ */
86
+ public static function get_fb_retailer_id($woo_product) {
87
+ $woo_id = $woo_product->get_id();
88
+
89
+ // Call $woo_product->get_id() instead of ->id to account for Variable
90
+ // products, which have their own variant_ids.
91
+ return $woo_product->get_sku() ? $woo_product->get_sku() . '_' .
92
+ $woo_id : self::FB_RETAILER_ID_PREFIX . $woo_id;
93
+ }
94
+
95
+ /**
96
+ * Return categories for products/pixel
97
+ *
98
+ * @access public
99
+ * @param String $id
100
+ * @return Array
101
+ */
102
+ public static function get_product_categories($wpid) {
103
+ $category_path = wp_get_post_terms(
104
+ $wpid,
105
+ 'product_cat',
106
+ array('fields' => 'all'));
107
+ $content_category = array_values(
108
+ array_map(
109
+ function($item) {
110
+ return $item->name;
111
+ },
112
+ $category_path));
113
+ $content_category_slice = array_slice($content_category, -1);
114
+ $categories =
115
+ empty($content_category) ? '""' : implode(', ', $content_category);
116
+ return array(
117
+ 'name' => array_pop($content_category_slice),
118
+ 'categories' => $categories
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Returns content id to match on for Pixel fires.
124
+ *
125
+ * @access public
126
+ * @param WC_Product $woo_product
127
+ * @return array
128
+ */
129
+ public static function get_fb_content_ids($woo_product) {
130
+ return array(self::get_fb_retailer_id($woo_product));
131
+ }
132
+
133
+ /**
134
+ * Clean up strings for FB Graph POSTing.
135
+ * This function should will:
136
+ * 1. Replace newlines chars/nbsp with a real space
137
+ * 2. strip_tags()
138
+ * 3. trim()
139
+ *
140
+ * @access public
141
+ * @param String string
142
+ * @return string
143
+ */
144
+ public static function clean_string($string) {
145
+ $string = do_shortcode($string);
146
+ $string = str_replace(array('&amp%3B', '&amp;'), '&', $string);
147
+ $string = str_replace(array("\r", "&nbsp;", "\t"), ' ', $string);
148
+ $string = wp_strip_all_tags($string, false); // true == remove line breaks
149
+ return $string;
150
+ }
151
+
152
+ /**
153
+ * Returns flat array of woo IDs for variable products, or
154
+ * an array with a single woo ID for simple products.
155
+ *
156
+ * @access public
157
+ * @param WC_Product $woo_product
158
+ * @return array
159
+ */
160
+ public static function get_product_array($woo_product) {
161
+ $result = array();
162
+ if (WC_Facebookcommerce_Utils::is_variable_type($woo_product->get_type())) {
163
+ foreach ($woo_product->get_children() as $item_id) {
164
+ array_push($result, $item_id);
165
+ }
166
+ return $result;
167
+ } else {
168
+ return array($woo_product->get_id());
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Returns true if WooCommerce plugin found.
174
+ *
175
+ * @access public
176
+ * @return bool
177
+ */
178
+ public static function isWoocommerceIntegration() {
179
+ return class_exists('WooCommerce');
180
+ }
181
+
182
+ /**
183
+ * Returns integration dependent name.
184
+ *
185
+ * @access public
186
+ * @return string
187
+ */
188
+ public static function getIntegrationName() {
189
+ if (WC_Facebookcommerce_Utils::isWoocommerceIntegration()) {
190
+ return 'WooCommerce';
191
+ } else {
192
+ return 'WordPress';
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Returns user info for the current WP user.
198
+ *
199
+ * @access public
200
+ * @param boolean $use_pii
201
+ * @return array
202
+ */
203
+ public static function get_user_info($use_pii) {
204
+ $current_user = wp_get_current_user();
205
+ if (0 === $current_user->ID || $use_pii === false) {
206
+ // User not logged in or admin chose not to send PII.
207
+ return array();
208
+ } else {
209
+ return array_filter(
210
+ array(
211
+ // Keys documented in
212
+ // https://developers.facebook.com/docs/facebook-pixel/pixel-with-ads/
213
+ // /conversion-tracking#advanced_match
214
+ 'em' => $current_user->user_email,
215
+ 'fn' => $current_user->user_firstname,
216
+ 'ln' => $current_user->user_lastname
217
+ ),
218
+ function ($value) { return $value !== null && $value !== ''; });
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Utility function for development logging.
224
+ */
225
+ public static function fblog(
226
+ $message,
227
+ $object = array(),
228
+ $error = false,
229
+ $ems = '') {
230
+ if ($error) {
231
+ $object['plugin_version'] = self::PLUGIN_VERSION;
232
+ $object['php_version'] = phpversion();
233
+ }
234
+ $message = json_encode(array(
235
+ 'message' => $message,
236
+ 'object' => $object
237
+ ));
238
+ $ems = $ems ?: self::$ems;
239
+ if ($ems) {
240
+ self::$fbgraph->log(
241
+ $ems,
242
+ $message,
243
+ $error);
244
+ } else {
245
+ error_log('external merchant setting is null, something wrong here: ' .
246
+ $message);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Utility function for development Tip Events logging.
252
+ */
253
+ public static function tip_events_log(
254
+ $tip_id,
255
+ $channel_id,
256
+ $event,
257
+ $ems = '') {
258
+
259
+ $ems = $ems ?: self::$ems;
260
+ if ($ems) {
261
+ self::$fbgraph->log_tip_event(
262
+ $tip_id,
263
+ $channel_id,
264
+ $event);
265
+ } else {
266
+ error_log('external merchant setting is null');
267
+ }
268
+ }
269
+
270
+ public static function is_variation_type($type) {
271
+ return $type == 'variation' || $type == 'subscription_variation';
272
+ }
273
+
274
+ public static function is_variable_type($type) {
275
+ return $type == 'variable' || $type == 'variable-subscription';
276
+ }
277
+
278
+ public static function check_woo_ajax_permissions($action_text, $die) {
279
+ if (!current_user_can('manage_woocommerce')) {
280
+ self::log(
281
+ 'Non manage_woocommerce user attempting to'.$action_text.'!',
282
+ array(),
283
+ true);
284
+ if ($die) {
285
+ wp_die();
286
+ }
287
+ return false;
288
+ }
289
+ return true;
290
+ }
291
+
292
+ /**
293
+ * Returns true if id is a positive non-zero integer
294
+ *
295
+ * @access public
296
+ * @param string $pixel_id
297
+ * @return bool
298
+ */
299
+ public static function is_valid_id($pixel_id) {
300
+ return isset($pixel_id) && is_numeric($pixel_id) && (int)$pixel_id > 0;
301
+ }
302
+
303
+ /**
304
+ * Helper function to query posts.
305
+ */
306
+ public static function get_wp_posts(
307
+ $product_group_id = null,
308
+ $compare_condition = null,
309
+ $post_type = 'product') {
310
+ $args = array(
311
+ 'fields' => 'ids',
312
+ 'meta_query' => array(
313
+ (($product_group_id) ?
314
+ array(
315
+ 'key' => $product_group_id,
316
+ 'compare' => $compare_condition,
317
+ ) : array()
318
+ ),
319
+ ),
320
+ 'post_status' => 'publish',
321
+ 'post_type' => $post_type,
322
+ 'posts_per_page' => -1,
323
+ );
324
+ return get_posts($args);
325
+ }
326
+
327
+ /**
328
+ * Helper log function for debugging
329
+ */
330
+ public static function log($message) {
331
+ if (WP_DEBUG === true) {
332
+ if (is_array($message) || is_object($message)) {
333
+ error_log(json_encode($message));
334
+ }
335
+ else {
336
+ error_log(sanitize_textarea_field($message));
337
+ }
338
+ }
339
+ }
340
+
341
+ // Return store name with sanitized apostrophe
342
+ public static function get_store_name() {
343
+ if (self::$store_name) {
344
+ return self::$store_name;
345
+ }
346
+ $name = trim(str_replace(
347
+ "'",
348
+ "\u{2019}",
349
+ html_entity_decode(
350
+ get_bloginfo('name'),
351
+ ENT_QUOTES,
352
+ 'UTF-8')));
353
+ if ($name) {
354
+ self::$store_name = $name;
355
+ return $name;
356
+ }
357
+ // Fallback to site url
358
+ $url = get_site_url();
359
+ if ($url) {
360
+ self::$store_name = parse_url($url, PHP_URL_HOST);
361
+ return self::$store_name;
362
+ }
363
+ // If site url doesn't exist, fall back to http host.
364
+ if ($_SERVER['HTTP_HOST']) {
365
+ self::$store_name = $_SERVER['HTTP_HOST'];
366
+ return self::$store_name;
367
+ }
368
+
369
+ // If http host doesn't exist, fall back to local host name.
370
+ $url = gethostname();
371
+ self::$store_name = $url;
372
+ return (self::$store_name) ? (self::$store_name) : 'A Store Has No Name';
373
+ }
374
+
375
+ /*
376
+ * Change variant product field name from Woo taxonomy to FB name
377
+ */
378
+ public static function sanitize_variant_name($name) {
379
+ $name = str_replace(array('attribute_', 'pa_'), '', strtolower($name));
380
+
381
+ // British spelling
382
+ if ($name === self::FB_VARIANT_COLOUR) {
383
+ $name = self::FB_VARIANT_COLOR;
384
+ }
385
+
386
+ switch ($name) {
387
+ case self::FB_VARIANT_SIZE:
388
+ case self::FB_VARIANT_COLOR:
389
+ case self::FB_VARIANT_GENDER:
390
+ case self::FB_VARIANT_PATTERN:
391
+ break;
392
+ default:
393
+ $name = 'custom_data:' . strtolower($name);
394
+ break;
395
+ }
396
+
397
+ return $name;
398
+ }
399
+
400
+ public static function validateGender($gender) {
401
+ if ($gender && !isset(self::$validGenderArray[$gender])) {
402
+ $first_char = strtolower(substr($gender, 0, 1));
403
+ // Men, Man, Boys
404
+ if ($first_char === 'm' || $first_char === 'b') {
405
+ return "male";
406
+ }
407
+ // Women, Woman, Female, Ladies
408
+ if ($first_char === 'w' || $first_char === 'f' || $first_char === 'l') {
409
+ return "female";
410
+ }
411
+ if ($first_char === 'u') {
412
+ return "unisex";
413
+ }
414
+ if (strlen($gender) >= 3) {
415
+ $gender = strtolower(substr($gender, 0, 3));
416
+ if ($gender === 'gir' || $gender === 'her') {
417
+ return "female";
418
+ }
419
+ if ($gender === 'him' || $gender === 'his' || $gender == 'guy') {
420
+ return "male";
421
+ }
422
+ }
423
+ return null;
424
+ }
425
+ return $gender;
426
+ }
427
+
428
+ public static function get_fbid_post_meta($wp_id, $fbid_type) {
429
+ return get_post_meta($wp_id, $fbid_type, true);
430
+ }
431
+
432
+ public static function is_all_caps($value) {
433
+ if ($value === null || $value === '') {
434
+ return true;
435
+ }
436
+ if (preg_match('/[^\\p{Common}\\p{Latin}]/u', $value)) {
437
+ // Contains non-western characters
438
+ // So, it can't be all uppercase
439
+ return false;
440
+ }
441
+ $latin_string = preg_replace('/[^\\p{Latin}]/u', '', $value);
442
+ if ($latin_string === '') {
443
+ // Symbols only
444
+ return true;
445
+ }
446
+ return strtoupper($latin_string) === $latin_string;
447
+ }
448
+
449
+ public static function decode_json($json_string, $assoc = false) {
450
+ // Plugin requires 5.6.0 but for some user use 5.5.9 JSON_BIGINT_AS_STRING
451
+ // will cause 502 issue when redirect.
452
+ return version_compare(phpversion(), '5.6.0') >= 0
453
+ ? json_decode($json_string, $assoc, 512, JSON_BIGINT_AS_STRING)
454
+ : json_decode($json_string, $assoc, 512);
455
+ }
456
+
457
+ public static function set_test_fail_reason($msg, $trace) {
458
+ $reason_msg = get_transient('facebook_plugin_test_fail');
459
+ if ($reason_msg) {
460
+ $msg = $reason_msg . PHP_EOL . $msg;
461
+ }
462
+ set_transient('facebook_plugin_test_fail', $msg);
463
+ set_transient('facebook_plugin_test_stack_trace', $trace);
464
+ }
465
+
466
+ /**
467
+ * Helper function to check time cap.
468
+ */
469
+ public static function check_time_cap($from, $date_cap) {
470
+ if ($from == null) {
471
+ return true;
472
+ }
473
+ $now = new DateTime(current_time('mysql'));
474
+ $diff_in_day = $now->diff(new DateTime($from))->format('%a');
475
+ return is_numeric($diff_in_day) && (int)$diff_in_day > $date_cap;
476
+ }
477
+
478
+ public static function get_cached_best_tip() {
479
+ $cached_best_tip = WC_Facebookcommerce_Utils::decode_json(
480
+ get_option('fb_info_banner_last_best_tip', ''));
481
+ return $cached_best_tip;
482
+ }
483
+ }
484
+
485
+ endif;
includes/fbwpml.php ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (!defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ if (!class_exists('WC_Facebook_WPML_Injector')) :
16
+
17
+ class FB_WPML_Language_Status {
18
+ const VISIBLE = 1;
19
+ const HIDDEN = 2;
20
+ const NOT_SYNCED = 0;
21
+ }
22
+
23
+ class WC_Facebook_WPML_Injector {
24
+ public static $settings = null;
25
+ public static $default_lang = null;
26
+ const OPTION = 'fb_wmpl_language_visibility';
27
+
28
+ public function __construct() {
29
+ add_action('icl_menu_footer', array($this, 'wpml_support'));
30
+ add_action('icl_ajx_custom_call', array($this, 'wpml_ajax_support'), 10, 2);
31
+ self::$settings = get_option(self::OPTION);
32
+ self::$default_lang = apply_filters('wpml_default_language', null);
33
+ }
34
+
35
+ public static function should_hide($wp_id) {
36
+ $product_lang = apply_filters('wpml_post_language_details', null, $wp_id);
37
+ $settings = self::$settings;
38
+ if ($product_lang && isset($product_lang['language_code'])) {
39
+ $product_lang = $product_lang['language_code'];
40
+ }
41
+
42
+ // Option doesn't exist : Backwards Compatibility
43
+ if (!$settings) {
44
+ return ($product_lang && self::$default_lang !== $product_lang);
45
+ }
46
+ // Hide products from non-active languages.
47
+ if (!isset($settings[$product_lang])) {
48
+ return true;
49
+ }
50
+ return $settings[$product_lang] !== FB_WPML_Language_Status::VISIBLE;
51
+ }
52
+
53
+ public function wpml_ajax_support($call, $REQUEST) {
54
+ global $sitepress;
55
+ if (isset($REQUEST['icl_ajx_action'])) {
56
+ $call = $REQUEST['icl_ajx_action'];
57
+ }
58
+ if ($call === "icl_fb_woo") {
59
+ $active_languages = array_keys($sitepress->get_active_languages());
60
+ $settings = array();
61
+ foreach ($active_languages as $lang) {
62
+ $settings[$lang] = $REQUEST[$lang] === 'on' ?
63
+ FB_WPML_Language_Status::VISIBLE : FB_WPML_Language_Status::HIDDEN;
64
+ }
65
+
66
+ update_option('fb_wmpl_language_visibility', $settings, false);
67
+ self::$settings = $settings;
68
+ }
69
+ }
70
+
71
+ public function wpml_support() {
72
+ global $sitepress;
73
+ if (strpos($_GET['page'], 'languages.php')) {
74
+ $active_languages = $sitepress->get_active_languages();
75
+ $settings = get_option(self::OPTION);
76
+
77
+ // Default setting is only show default lang.
78
+ if (!$settings) {
79
+ $settings = array_fill_keys(
80
+ array_keys($active_languages), FB_WPML_Language_Status::HIDDEN);
81
+ $settings[self::$default_lang] = FB_WPML_Language_Status::VISIBLE;
82
+ }
83
+ $ajax_response = sprintf(
84
+ 'Saved. You should now '.
85
+ ' <a href="%s&fb_force_resync=true">Re-Sync</a>'.
86
+ ' your products with Facebook. ',
87
+ WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL);
88
+
89
+ ?><div id="lang-sec-fb" class="wpml-section wpml-section-languages">
90
+ <div class="wpml-section-header">
91
+ <h3><?php _e('Facebook Visibility', 'sitepress') ?></h3>
92
+ </div>
93
+ <div class="wpml-section-content">
94
+ WooCommerce Products with languages that are selected
95
+ here will be visible to customers who see your Facebook Shop.
96
+
97
+ <div class="wpml-section-content-inner">
98
+ <form id="icl_fb_woo" name="icl_fb_woo" action="">
99
+ <?php
100
+ foreach ($settings as $language => $set) {
101
+ $is_checked = $set === FB_WPML_Language_Status::VISIBLE ?
102
+ 'checked' : '';
103
+ $str = '
104
+ <p><label>
105
+ <input type="checkbox" id="icl_fb_woo_chk" name="'.$language.'" '.$is_checked.'>
106
+ '.$active_languages[$language]['native_name'].'
107
+ </label></p>
108
+ ';
109
+ echo $str;
110
+ }
111
+ ?>
112
+ <p class="buttons-wrap">
113
+ <span class="icl_ajx_response_fb" id="icl_ajx_response_fb" hidden="true">
114
+ <?php echo $ajax_response ?>
115
+ </span>
116
+ <input class="button button-primary"
117
+ name="save"
118
+ value="<?php _e('Save', 'sitepress') ?>"
119
+ type="submit" />
120
+ </p>
121
+ </form>
122
+ <script type="text/javascript">
123
+ addLoadEvent(function(){
124
+ jQuery('#icl_fb_woo').submit(iclSaveForm);
125
+ jQuery('#icl_fb_woo').submit(function(){
126
+ jQuery('#icl_ajx_response_fb').show();
127
+ });
128
+ });
129
+ </script>
130
+ </div>
131
+ </div>
132
+ </div><?php
133
+ }
134
+ }
135
+ }
136
+
137
+ endif;
includes/test/facebook-integration-test.php ADDED
@@ -0,0 +1,575 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ *
10
+ * usage:
11
+ * 1. set WP_DEBUG = true and WP_DEBUG_DISPLAY = false
12
+ * 2. append "&fb_test_product_sync=true" to the url when you are on facebook-for-woocommerce setting pages
13
+ * 3. refresh the page to launch test
14
+ * https://codex.wordpress.org/WP_DEBUG
15
+ */
16
+
17
+ if (!defined('ABSPATH')) {
18
+ exit;
19
+ }
20
+
21
+ include_once(dirname(__DIR__) . '/fbutils.php');
22
+ include_once('fbproductfeed-test.php');
23
+
24
+ if (!class_exists('WC_Facebook_Integration_Test')) :
25
+
26
+ /**
27
+ * This tests the upload of test objects into Facebook using the plugin's
28
+ * infrastructure and checks to see if the product field have been correctly
29
+ * uploaded into FB.
30
+ */
31
+ class WC_Facebook_Integration_Test {
32
+
33
+ const FB_PRODUCT_GROUP_ID = 'fb_product_group_id';
34
+ const FB_PRODUCT_ITEM_ID = 'fb_product_item_id';
35
+ const MAX_SLEEP_IN_SEC = 90;
36
+ const MAX_TIME = 'T23:59+00:00';
37
+ const MIN_TIME = 'T00:00+00:00';
38
+ /** Class Instance */
39
+ private static $instance;
40
+
41
+ public static $commerce = null; // Full WC_Facebookcommerce_Integration obj
42
+ public static $fbgraph = null;
43
+ public static $test_mode = false;
44
+
45
+ // simple products' id and variable products' parent_id
46
+ public static $wp_post_ids = array();
47
+ // FB product item retailer id.
48
+ public static $retailer_ids = array();
49
+ // product and product_variation post id for test
50
+ public $product_post_wpid = null;
51
+ public static $test_pass = 1;
52
+
53
+ /**
54
+ * Get the class instance
55
+ */
56
+ public static function get_instance($commerce) {
57
+ return null === self::$instance
58
+ ? (self::$instance = new self($commerce))
59
+ : self::$instance;
60
+ }
61
+
62
+ /**
63
+ * Constructor
64
+ */
65
+ public function __construct($commerce) {
66
+
67
+ self::$commerce = $commerce;
68
+
69
+ add_action('wp_ajax_ajax_test_sync_products_using_feed',
70
+ array($this, 'ajax_test_sync_products_using_feed'));
71
+ }
72
+
73
+ /**
74
+ * Test visible products by uploading feed.
75
+ **/
76
+ function ajax_test_sync_products_using_feed() {
77
+ self::$test_mode = true;
78
+ // test ajax reset all products in db
79
+ $reset = self::$commerce->reset_all_products();
80
+ if ($reset) {
81
+ WC_Facebookcommerce_Utils::log('Test - Removing FBIDs from all products');
82
+ $this->product_post_wpid = $this->create_data();
83
+ if (empty($this->product_post_wpid)) {
84
+ self::$test_pass = 0;
85
+ WC_Facebookcommerce_Utils::log(
86
+ 'Test - Fail to create test product by inserting posts.');
87
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
88
+ 'Fail to create test products by inserting posts.',
89
+ (new Exception)->getTraceAsString());
90
+ update_option('fb_test_pass', false);
91
+ wp_die();
92
+ return;
93
+ }
94
+ $this->set_product_wpid($this->product_post_wpid);
95
+ $upload_success =
96
+ self::$commerce->ajax_sync_all_fb_products_using_feed(true);
97
+ if ($upload_success) {
98
+ // verification Step.
99
+ // Wait till FB finish backend creation to prevent race condition.
100
+ $time_start = microtime(true);
101
+ while ((microtime(true) - $time_start) < self::MAX_SLEEP_IN_SEC) {
102
+ $complete = self::$commerce->fbproductfeed->is_upload_complete(
103
+ self::$commerce->settings);
104
+ if ($complete) {
105
+ break;
106
+ } else {
107
+ $this->sleep_til_upload_complete(10);
108
+ }
109
+ }
110
+ $this->sleep_til_upload_complete(60);
111
+ $check_product_create = $this->check_product_create();
112
+ if (!$check_product_create) {
113
+ self::$test_pass = 0;
114
+ } else {
115
+ WC_Facebookcommerce_Utils::log(
116
+ 'Test - Products create successfully.');
117
+ }
118
+ // Clean up whatever has been created.
119
+ // Test on_product_delete API hook.
120
+ $clean_up = $this->clean_up();
121
+ if (!$clean_up) {
122
+ self::$test_pass = 0;
123
+ WC_Facebookcommerce_Utils::log(
124
+ 'Test - Fail to delete product from FB');
125
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
126
+ 'Fail to delete product from FB',
127
+ (new Exception)->getTraceAsString());
128
+ } else {
129
+ WC_Facebookcommerce_Utils::log(
130
+ 'Test - Delete product from FB successfully');
131
+ }
132
+ } else {
133
+ self::$test_pass = 0;
134
+ WC_Facebookcommerce_Utils::log(
135
+ 'Test - Sync all products using feed, curl failed.');
136
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
137
+ 'Sync all products using feed, curl failed',
138
+ (new Exception)->getTraceAsString());
139
+ }
140
+
141
+ } else {
142
+ self::$test_pass = 0;
143
+ WC_Facebookcommerce_Utils::log(
144
+ 'Test - Fail to remove FBIDs from local DB');
145
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
146
+ 'Fail to remove FBIDs from local DB',
147
+ (new Exception)->getTraceAsString());
148
+ }
149
+ update_option('fb_test_pass', self::$test_pass);
150
+ wp_die();
151
+ return;
152
+ }
153
+
154
+ function check_product_create() {
155
+ if (count(self::$retailer_ids) < 3) {
156
+ WC_Facebookcommerce_Utils::log('Test - Failed to create 3 product items.');
157
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
158
+ 'Failed to create 3 product items.',
159
+ (new Exception)->getTraceAsString());
160
+ return false;
161
+ }
162
+
163
+ if (count(self::$retailer_ids) > 3) {
164
+ WC_Facebookcommerce_Utils::log(
165
+ 'Test - Failed to skip invisible products.');
166
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
167
+ 'Failed to skip invisible products.',
168
+ (new Exception)->getTraceAsString());
169
+ return false;
170
+ }
171
+
172
+ // Check 3 products have been created.
173
+ for ($i = 0; $i < 3; $i++) {
174
+ $product_type = $i == 0? 'Simple' : 'Variable';
175
+ $retailer_id = self::$retailer_ids[$i];
176
+ $item_fbid =
177
+ $this->check_fbid_api(self::FB_PRODUCT_ITEM_ID, $retailer_id);
178
+ $group_fbid =
179
+ $this->check_fbid_api(self::FB_PRODUCT_GROUP_ID, $retailer_id);
180
+ if (!$item_fbid || !$group_fbid) {
181
+ WC_Facebookcommerce_Utils::log('Test - ' . $product_type .
182
+ ' product failed to create.');
183
+ WC_Facebookcommerce_Utils::set_test_fail_reason($product_type .
184
+ ' product failed to create.', (new Exception)->getTraceAsString());
185
+ return false;
186
+ }
187
+ }
188
+
189
+ // Check product detailed as expected.
190
+ $data = array(
191
+ 'name' => 'a simple product for test',
192
+ 'price' => '20.00',
193
+ 'description' => 'This is to test a simple product.',
194
+ 'sale_price' => '10.00',
195
+ 'sale_price_dates_from' =>
196
+ date_i18n('Y-m-d', strtotime('now')) . self::MIN_TIME,
197
+ 'sale_price_dates_to' =>
198
+ date_i18n('Y-m-d', strtotime('+10 day')) . self::MAX_TIME,
199
+ 'visibility' => 'published',
200
+ );
201
+ $simple_product_result =
202
+ $this->check_product_info(self::$retailer_ids[0], false, $data);
203
+ if (!$simple_product_result) {
204
+ WC_Facebookcommerce_Utils::log('Test - Simple product failed to match ' .
205
+ 'product details.');
206
+ WC_Facebookcommerce_Utils::set_test_fail_reason('Simple product failed to'
207
+ . ' match product details.', (new Exception)->getTraceAsString());
208
+ return false;
209
+ }
210
+
211
+ $data = array(
212
+ 'name' => 'a variable product for test',
213
+ 'price' => '30.00',
214
+ 'description' => 'This is to test a variable product. - Red',
215
+ 'additional_variant_attributes' => array('value' => 'Red'),
216
+ 'visibility' => 'published',
217
+ );
218
+ $variable_product_result =
219
+ $this->check_product_info(self::$retailer_ids[1], true, $data);
220
+ if (!$variable_product_result) {
221
+ WC_Facebookcommerce_Utils::log(
222
+ 'Test - Variable product failed to match product details.');
223
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
224
+ 'Variable product failed to match product details.',
225
+ (new Exception)->getTraceAsString());
226
+ return false;
227
+ }
228
+ return true;
229
+ }
230
+
231
+ function check_fbid_api($fbid_type, $fb_retailer_id) {
232
+ $product_fbid_result = self::$fbgraph->get_facebook_id(
233
+ self::$commerce->product_catalog_id,
234
+ $fb_retailer_id,
235
+ true);
236
+
237
+ if (is_wp_error($product_fbid_result)) {
238
+ WC_Facebookcommerce_Utils::log(
239
+ 'Test - ' . $product_fbid_result->get_error_message());
240
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
241
+ 'There was an issue connecting to the Facebook API: '.
242
+ $product_fbid_result->get_error_message(),
243
+ (new Exception)->getTraceAsString());
244
+ return false;
245
+ }
246
+
247
+ if ($product_fbid_result && isset($product_fbid_result['body'])) {
248
+ $body = WC_Facebookcommerce_Utils::decode_json(
249
+ $product_fbid_result['body'], true);
250
+ if ($body && isset($body['id'])) {
251
+ if ($fbid_type == self::FB_PRODUCT_GROUP_ID) {
252
+ $fb_id =
253
+ isset($body['product_group'])
254
+ ? $body['product_group']['id']
255
+ : false;
256
+ } else {
257
+ $fb_id = $body['id'];
258
+ }
259
+ return $fb_id;
260
+ }
261
+ }
262
+
263
+ return false;
264
+ }
265
+
266
+ function check_product_info($retailer_id, $has_variant, $data) {
267
+ $prod_info_result = self::$fbgraph->check_product_info(
268
+ self::$commerce->product_catalog_id,
269
+ $retailer_id,
270
+ $has_variant);
271
+ if (is_wp_error($prod_info_result)) {
272
+ WC_Facebookcommerce_Utils::log(
273
+ 'Test - ' . $prod_info_result->get_error_message());
274
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
275
+ 'There was an issue connecting to the Facebook API: '.
276
+ $prod_info_result->get_error_message(),
277
+ (new Exception)->getTraceAsString());
278
+ return false;
279
+ }
280
+
281
+ $match = true;
282
+ if ($prod_info_result && isset($prod_info_result['body'])) {
283
+ $body = WC_Facebookcommerce_Utils::decode_json(
284
+ $prod_info_result['body'], true);
285
+ if (!$body) {
286
+ return false;
287
+ }
288
+ if ($body['name'] != $data['name']) {
289
+ WC_Facebookcommerce_Utils::log(
290
+ 'Test - ' . $retailer_id . " doesn\'t match name.");
291
+ $match = false;
292
+ }
293
+
294
+ if ($body['description'] != $data['description']) {
295
+ WC_Facebookcommerce_Utils::log(
296
+ 'Test - ' . $retailer_id . " doesn\'t match description.");
297
+ $match = false;
298
+ }
299
+ // Woo doesn't have API to return currency symbol.
300
+ // FB graph API only support to response with a currency symbol price.
301
+ // No php built-in function to support cast html number to symbol.
302
+ // Compare numeric price only.
303
+ $price = floatval(preg_replace('/[^\d\.]+/', '', $body['price']));
304
+ if ($price != $data['price']) {
305
+ WC_Facebookcommerce_Utils::log(
306
+ 'Test - ' . $retailer_id . " doesn\'t match price.");
307
+ $match = false;
308
+ }
309
+ // Check sale price and dates.
310
+ if (isset($data['sale_price'])) {
311
+ $sale_price = floatval(
312
+ preg_replace('/[^\d\.]+/', '', $body['sale_price']));
313
+ if ($sale_price != $data['sale_price']) {
314
+ WC_Facebookcommerce_Utils::log(
315
+ 'Test - ' . $retailer_id . " doesn\'t match sale price.");
316
+ $match = false;
317
+ }
318
+ if ($body['sale_price_start_date'] != $data['sale_price_dates_from']) {
319
+ WC_Facebookcommerce_Utils::log(
320
+ 'Test - ' . $retailer_id . " doesn\'t match sale price start date");
321
+ $match = false;
322
+ }
323
+ if ($body['sale_price_end_date'] != $data['sale_price_dates_to']) {
324
+ WC_Facebookcommerce_Utils::log(
325
+ 'Test - ' . $retailer_id . " doesn\'t match sale price end date.");
326
+ $match = false;
327
+ }
328
+ }
329
+
330
+ if ($body['visibility'] != $data['visibility']) {
331
+ WC_Facebookcommerce_Utils::log(
332
+ 'Test - ' . $retailer_id . " doesn\'t match visibility.");
333
+ $match = false;
334
+ }
335
+
336
+ if ($has_variant &&
337
+ (!isset($body['additional_variant_attributes']) ||
338
+ $body['additional_variant_attributes'][0]['value'] !=
339
+ $data['additional_variant_attributes']['value'])) {
340
+
341
+ WC_Facebookcommerce_Utils::log(
342
+ 'Test - ' . $retailer_id . " doesn\'t match variation.");
343
+ $match = false;
344
+ }
345
+ }
346
+ return $match;
347
+ }
348
+
349
+ // Don't early return to prevent haunting product id.
350
+ function clean_up() {
351
+ $failure = false;
352
+ foreach (self::$wp_post_ids as $post_id) {
353
+ $delete_post_result = wp_delete_post($post_id);
354
+ // return false or null if failed.
355
+ if (!$delete_post_result) {
356
+ WC_Facebookcommerce_Utils::log('Test - Fail to delete post ' . $post_id);
357
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
358
+ 'Fail to delete post ' . $post_id, (new Exception)->getTraceAsString());
359
+ $failure = true;
360
+ }
361
+ }
362
+ self::$wp_post_ids = array();
363
+
364
+ $this->sleep_til_upload_complete(60);
365
+ foreach (self::$retailer_ids as $retailer_id) {
366
+ $item_fbid =
367
+ $this->check_fbid_api(self::FB_PRODUCT_ITEM_ID, $retailer_id);
368
+ $group_fbid =
369
+ $this->check_fbid_api(self::FB_PRODUCT_GROUP_ID, $retailer_id);
370
+ if ($item_fbid || $group_fbid) {
371
+ WC_Facebookcommerce_Utils::log('Test - Failed to delete product ' .
372
+ $retailer_id . ' via plugin deletion hook.');
373
+ WC_Facebookcommerce_Utils::set_test_fail_reason(
374
+ 'Failed to delete product ' . $retailer_id .
375
+ ' via plugin deletion hook.',
376
+ (new Exception)->getTraceAsString());
377
+ $failure = true;
378
+ }
379
+ }
380
+ self::$retailer_ids = array();
381
+
382
+ return !$failure;
383
+ }
384
+
385
+ function create_data() {
386
+ $prod_and_variant_wpid = array();
387
+ // Gets term object from Accessories in the database.
388
+ $term = get_term_by('name', 'Accessories', 'product_cat');
389
+ // Accessories should be a default category.
390
+ // If not exist, set categories term first.
391
+ if (!$term) {
392
+ $term = wp_insert_term(
393
+ 'Accessories', // the term
394
+ 'product_cat', // the taxonomy
395
+ array(
396
+ 'slug' => 'accessories'
397
+ ));
398
+ }
399
+ $data = array (
400
+ 'post_content' => 'This is to test a simple product.',
401
+ 'post_title' => 'a simple product for test',
402
+ 'post_status' => 'publish',
403
+ 'post_type' => 'product',
404
+ 'term' => $term,
405
+ 'price' => 20,
406
+ 'sale_price' => 10,
407
+ 'sale_price_dates_from' => strtotime('now'),
408
+ 'sale_price_dates_to' => strtotime('+10 day'),
409
+ );
410
+ $simple_product_result =
411
+ $this->create_test_simple_product($data, $prod_and_variant_wpid);
412
+
413
+ if (!$simple_product_result) {
414
+ return false;
415
+ }
416
+
417
+ // Test an invisible product - invisible products won't be synced by feed.
418
+ $data['visibility'] = false;
419
+ $simple_product_result =
420
+ $this->create_test_simple_product($data, $prod_and_variant_wpid);
421
+
422
+ if (!$simple_product_result) {
423
+ return false;
424
+ }
425
+
426
+ $data['post_content'] = 'This is to test a variable product.';
427
+ $data['post_title'] = 'a variable product for test';
428
+ $data['price'] = 30;
429
+
430
+ // Test variable products.
431
+ $variable_product_result =
432
+ $this->create_test_variable_product($data, $prod_and_variant_wpid);
433
+ if (!$variable_product_result) {
434
+ return false;
435
+ }
436
+ return $prod_and_variant_wpid;
437
+ }
438
+
439
+ function create_test_simple_product($data, &$prod_and_variant_wpid) {
440
+ $post_id = $this->fb_insert_post($data, 'Simple');
441
+ if (!$post_id) {
442
+ return false;
443
+ }
444
+ array_push($prod_and_variant_wpid, $post_id);
445
+ update_post_meta($post_id, '_regular_price', $data['price']);
446
+ update_post_meta($post_id, '_sale_price', $data['sale_price']);
447
+ update_post_meta($post_id, '_sale_price_dates_from', $data['sale_price_dates_from']);
448
+ update_post_meta($post_id, '_sale_price_dates_to', $data['sale_price_dates_to']);
449
+
450
+ wp_set_object_terms($post_id, 'simple', 'product_type');
451
+ // Invisible products won't be synced by feed.
452
+ if (isset($data['visibility'])) {
453
+ $terms = array('exclude-from-catalog', 'exclude-from-search');
454
+ wp_set_object_terms($post_id, $terms, 'product_visibility');
455
+ } else {
456
+ array_push(self::$wp_post_ids, $post_id);
457
+ array_push(self::$retailer_ids, 'wc_post_id_' . $post_id);
458
+ }
459
+
460
+ $product = wc_get_product($post_id);
461
+ $product->set_stock_status('instock');
462
+ wp_set_object_terms($post_id, $data['term']->term_id, 'product_cat');
463
+ return true;
464
+ }
465
+
466
+ function create_test_variable_product($data, &$prod_and_variant_wpid) {
467
+ $post_id = $this->fb_insert_post($data, 'Variable');
468
+ if (!$post_id) {
469
+ return false;
470
+ }
471
+
472
+ wp_set_object_terms($post_id, 'variable', 'product_type');
473
+ array_push($prod_and_variant_wpid, $post_id);
474
+ array_push(self::$wp_post_ids, $post_id);
475
+ // Gets term object from Accessories in the database.
476
+ $term = get_term_by('name', 'Accessories', 'product_cat');
477
+ wp_set_object_terms($post_id, $term->term_id, 'product_cat');
478
+
479
+ // Set up attributes.
480
+ $avail_attribute_values = array(
481
+ 'Red',
482
+ 'Blue'
483
+ );
484
+ wp_set_object_terms($post_id, $avail_attribute_values, 'pa_color');
485
+ $thedata = array(
486
+ 'pa_color' => array(
487
+ 'name' => 'pa_color',
488
+ 'value' => '',
489
+ 'is_visible' => '1',
490
+ 'is_variation' => '1',
491
+ 'is_taxonomy' => '1'
492
+ )
493
+ );
494
+ update_post_meta($post_id, '_product_attributes', $thedata);
495
+
496
+ // Insert variations.
497
+ $variation_data = array(
498
+ 'post_content' => 'This is to test a variable product. - Red',
499
+ 'post_status' => 'publish',
500
+ 'post_type' => 'product_variation',
501
+ 'post_parent' => $post_id,
502
+ 'price' => 30,
503
+ );
504
+ $variation_red = $this->fb_insert_post($variation_data, 'Variation');
505
+ if (!$variation_red) {
506
+ return;
507
+ }
508
+
509
+ $this->fb_update_variation_meta(
510
+ $prod_and_variant_wpid, $variation_red, 'Red', $variation_data);
511
+
512
+ $variation_data['post_content'] = 'a variable product for test - Blue';
513
+ $variation_blue = $this->fb_insert_post($variation_data, 'Variatoin');
514
+ if (!$variation_blue) {
515
+ return false;
516
+ }
517
+ $this->fb_update_variation_meta(
518
+ $prod_and_variant_wpid, $variation_blue, 'Blue', $variation_data);
519
+ $product = wc_get_product($variation_blue);
520
+ $product->set_stock_status('instock');
521
+ wp_set_object_terms($variation_blue, 'variation', 'product_type');
522
+ return true;
523
+ }
524
+
525
+ function fb_update_variation_meta(
526
+ &$prod_and_variant_wpid,
527
+ $variation_id,
528
+ $value,
529
+ $data) {
530
+ array_push($prod_and_variant_wpid, $variation_id);
531
+ array_push(self::$retailer_ids, 'wc_post_id_' . $variation_id);
532
+
533
+ $attribute_term = get_term_by('name', $value, 'pa_color');
534
+
535
+ update_post_meta($variation_id, 'attribute_pa_color', $attribute_term->slug);
536
+ update_post_meta($variation_id, '_price', $data['price']);
537
+ update_post_meta($variation_id, '_regular_price', $data['price']);
538
+ wp_set_object_terms($variation_id, 'variation', 'product_type');
539
+ $product = wc_get_product($variation_id);
540
+ $product->set_stock_status('instock');
541
+ }
542
+
543
+ function fb_insert_post($data, $p_type) {
544
+ $postarr = array_intersect_key(
545
+ $data,
546
+ array_flip(array(
547
+ 'post_content',
548
+ 'post_title',
549
+ 'post_status',
550
+ 'post_type',
551
+ 'post_parent',
552
+ )));
553
+ $post_id = wp_insert_post($postarr);
554
+ if (is_wp_error($post_id)) {
555
+ WC_Facebookcommerce_Utils::log('Test - ' . $p_type .
556
+ ' product wp_insert_post' . 'failed: ' . json_encode($post_id));
557
+ return false;
558
+ } else {
559
+ return $post_id;
560
+ }
561
+ }
562
+
563
+ /**
564
+ * IMPORTANT! Wait for Ents creation and prevent race condition.
565
+ **/
566
+ function sleep_til_upload_complete($sec) {
567
+ sleep($sec);
568
+ }
569
+
570
+ function set_product_wpid($product_post_wpid) {
571
+ WC_Facebook_Product_Feed_Test_Mock::$product_post_wpid = $product_post_wpid;
572
+ }
573
+ }
574
+
575
+ endif;
includes/test/fbproductfeed-test.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
4
+ *
5
+ * This source code is licensed under the license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @package FacebookCommerce
9
+ */
10
+
11
+ if (! defined('ABSPATH')) {
12
+ exit;
13
+ }
14
+
15
+ include_once(dirname(__DIR__) . '/fbproductfeed.php');
16
+ include_once(dirname(__DIR__) . '/fbutils.php');
17
+
18
+ if (! class_exists('WC_Facebook_Product_Feed_Test')) :
19
+ /**
20
+ * Mock for Facebook feed class
21
+ */
22
+ class WC_Facebook_Product_Feed_Test_Mock extends WC_Facebook_Product_Feed {
23
+
24
+ public static $product_post_wpid = null;
25
+
26
+ // Return test product post id.
27
+ // Don't mess up actual products.
28
+ public function get_product_wpid() {
29
+ return self::$product_post_wpid;
30
+ }
31
+
32
+ // Log progress in local log file for testing.
33
+ // Not to overwhelm DB log to track important signals.
34
+ public function log_feed_progress($msg, $object = array()) {
35
+ $msg = empty($object) ? $msg : $msg . json_encode($object);
36
+ WC_Facebookcommerce_Utils::log('Test - ' . $msg);
37
+ }
38
+ }
39
+
40
+ endif;
readme.txt ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === Facebook for WooCommerce ===
2
+ Contributors: facebook
3
+ Tags: facebook, shop, catalog, advertise, pixel, product
4
+ Requires at least: 4.4
5
+ Tested up to: 4.9.8
6
+ Stable tag: 1.9.5
7
+ Requires PHP: 5.6
8
+ MySQL: 5.6 or greater
9
+ License: GPLv2 or later
10
+ License URI: https://www.gnu.org/licenses/gpl-2.0.html
11
+
12
+ Get the Official Facebook for WooCommerce plugin for two powerful ways to help grow your business, including an ads extension and shops tab for your page.
13
+
14
+ == Description ==
15
+
16
+ This is the official Facebook for WooCommerce plugin that connects your WooCommerce website to Facebook. With this plugin, you can install the Facebook pixel, upload your online store catalog, and create a shop on your Facebook page, enabling you to easily run dynamic ads.
17
+
18
+ Marketing on Facebook helps your business build lasting relationships with people, find new customers, and increase sales for your online store. With this Facebook ad extension, reaching the people who matter most to your business is simple. This extension will track the results of your advertising across devices. It will also help you:
19
+
20
+ * Maximize your campaign performance. By setting up the Facebook pixel and building your audience, you will optimize your ads for people likely to buy your products, and reach people with relevant ads on Facebook after they’ve visited your website.
21
+ * Find more customers. Connecting your product catalog automatically creates carousel ads that showcase the products you sell and attract more shoppers to your website.
22
+ * Generate sales among your website visitors. When you set up the Facebook pixel and connect your product catalog, you can use dynamic ads to reach shoppers when they’re on Facebook with ads for the products they viewed on your website. This will be included in a future release of Facebook for WooCommerce.
23
+
24
+ == Installation ==
25
+
26
+ Visit the Facebook Help Center [here](https://www.facebook.com/business/help/900699293402826).
27
+
28
+ == Support ==
29
+
30
+ If you believe you have found a security vulnerability on Facebook, we encourage you to let us know right away. We investigate all legitimate reports and do our best to quickly fix the problem. Before reporting, please review [this page](https://www.facebook.com/whitehat), which includes our responsible disclosure policy and reward guideline. You can submit bugs [here](https://github.com/facebookincubator/facebook-for-woocommerce/issues) or contact advertising support [here](https://www.facebook.com/business/help/900699293402826).
31
+
32
+ When opening a bug on GitHub, please give us as many details as possible.
33
+
34
+ * Symptoms of your problem
35
+ * Screenshot, if possible
36
+ * Your Facebook page URL
37
+ * Your website URL
38
+ * Current version of Facebook-for-WooCommerce, WooCommerce, Wordpress, PHP
39
+
40
+ == Changelog ==
41
+
42
+ = 1.9.11 - 2019-02-26 =
43
+ * changing contributor to facebook from facebook4woocommerce, so that
44
+ woo plugin will be shown under
45
+ https://profiles.wordpress.org/facebook/#content-plugins
46
+ * adding changelog in readme.txt so that notifications will be sent for
47
+ updates and changelog will be shown under
48
+ https://wordpress.org/plugins/facebook-for-woocommerce/#developers
49
+ * removing debug flags notice under facebook-for-woocommerce.php so that
50
+ developers will be able to debug with debug logs
51
+
52
+ = 1.9.10 - 2019-02-11 =
53
+ * Add facebook support link, this will help merchants to reach out to facebook customer service.
54
+ * Make plugin wordpress compatible by removing woocommerce updater and removing woo_include
55
+
56
+ = 1.9.9 - 2018-12-30 =
57
+ * Fix issue with missing file in v1.9.8
58
+ * Remove misleading content relating to Instagram which is not launched yet.