With the new group system for displaying ads we no longer can check if a user belongs to a trust level group lower than specified. The other problem is that ALL users including staff and higher trust levels all belong to trust level 0. So without this fix if we say that an ad should be visible to trust level 0 users then it will be shown to all users. This fix adds a new default setting for each ad platform for excluding trust level 3, 4, and staff users from being shown ads. - Make display_groups hidden (they will be removed in a later commit) - Switch to using only exclude_groups instead of display groups and exclude groups - rename showToDisplayGroups to showXAds for each provider
425 lines
10 KiB
JavaScript
Executable File
425 lines
10 KiB
JavaScript
Executable File
import { alias } from "@ember/object/computed";
|
|
import { htmlSafe } from "@ember/template";
|
|
import RSVP from "rsvp";
|
|
import loadScript from "discourse/lib/load-script";
|
|
import { isTesting } from "discourse-common/config/environment";
|
|
import discourseComputed, { on } from "discourse-common/utils/decorators";
|
|
import AdComponent from "discourse/plugins/discourse-adplugin/discourse/components/ad-component";
|
|
|
|
let _loaded = false,
|
|
_promise = null,
|
|
ads = {},
|
|
nextSlotNum = 1,
|
|
renderCounts = {};
|
|
|
|
function getNextSlotNum() {
|
|
return nextSlotNum++;
|
|
}
|
|
|
|
function splitWidthInt(value) {
|
|
let str = value.substring(0, 3);
|
|
return str.trim();
|
|
}
|
|
|
|
function splitHeightInt(value) {
|
|
let str = value.substring(4, 7);
|
|
return str.trim();
|
|
}
|
|
|
|
// This creates an array for the values of the custom targeting key
|
|
function valueParse(value) {
|
|
let final = value.replace(/ /g, "");
|
|
final = final.replace(/['"]+/g, "");
|
|
final = final.split(",");
|
|
return final;
|
|
}
|
|
|
|
// This creates an array for the key of the custom targeting key
|
|
function keyParse(word) {
|
|
let key = word;
|
|
key = key.replace(/['"]+/g, "");
|
|
key = key.split("\n");
|
|
return key;
|
|
}
|
|
|
|
// This should call adslot.setTargeting(key for that location, value for that location)
|
|
function custom_targeting(key_array, value_array, adSlot) {
|
|
for (let i = 0; i < key_array.length; i++) {
|
|
if (key_array[i]) {
|
|
adSlot.setTargeting(key_array[i], valueParse(value_array[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
const DESKTOP_SETTINGS = {
|
|
"topic-list-top": {
|
|
code: "dfp_topic_list_top_code",
|
|
sizes: "dfp_topic_list_top_ad_sizes",
|
|
targeting_keys: "dfp_target_topic_list_top_key_code",
|
|
targeting_values: "dfp_target_topic_list_top_value_code",
|
|
},
|
|
"topic-above-post-stream": {
|
|
code: "dfp_topic_above_post_stream_code",
|
|
sizes: "dfp_topic_above_post_stream_ad_sizes",
|
|
targeting_keys: "dfp_target_topic_above_post_stream_key_code",
|
|
targeting_values: "dfp_target_topic_above_post_stream_value_code",
|
|
},
|
|
"topic-above-suggested": {
|
|
code: "dfp_topic_above_suggested_code",
|
|
sizes: "dfp_topic_above_suggested_ad_sizes",
|
|
targeting_keys: "dfp_target_topic_above_suggested_key_code",
|
|
targeting_values: "dfp_target_topic_above_suggested_value_code",
|
|
},
|
|
"post-bottom": {
|
|
code: "dfp_post_bottom_code",
|
|
sizes: "dfp_post_bottom_ad_sizes",
|
|
targeting_keys: "dfp_target_post_bottom_key_code",
|
|
targeting_values: "dfp_target_post_bottom_value_code",
|
|
},
|
|
};
|
|
|
|
const MOBILE_SETTINGS = {
|
|
"topic-list-top": {
|
|
code: "dfp_mobile_topic_list_top_code",
|
|
sizes: "dfp_mobile_topic_list_top_ad_sizes",
|
|
targeting_keys: "dfp_target_topic_list_top_key_code",
|
|
targeting_values: "dfp_target_topic_list_top_value_code",
|
|
},
|
|
"topic-above-post-stream": {
|
|
code: "dfp_mobile_topic_above_post_stream_code",
|
|
sizes: "dfp_mobile_topic_above_post_stream_ad_sizes",
|
|
targeting_keys: "dfp_target_topic_above_post_stream_key_code",
|
|
targeting_values: "dfp_target_topic_above_post_stream_value_code",
|
|
},
|
|
"topic-above-suggested": {
|
|
code: "dfp_mobile_topic_above_suggested_code",
|
|
sizes: "dfp_mobile_topic_above_suggested_ad_sizes",
|
|
targeting_keys: "dfp_target_topic_above_suggested_key_code",
|
|
targeting_values: "dfp_target_topic_above_suggested_value_code",
|
|
},
|
|
"post-bottom": {
|
|
code: "dfp_mobile_post_bottom_code",
|
|
sizes: "dfp_mobile_post_bottom_ad_sizes",
|
|
targeting_keys: "dfp_target_post_bottom_key_code",
|
|
targeting_values: "dfp_target_post_bottom_value_code",
|
|
},
|
|
};
|
|
|
|
function getWidthAndHeight(placement, settings, isMobile) {
|
|
let config, size;
|
|
|
|
if (isMobile) {
|
|
config = MOBILE_SETTINGS[placement];
|
|
} else {
|
|
config = DESKTOP_SETTINGS[placement];
|
|
}
|
|
|
|
if (!renderCounts[placement]) {
|
|
renderCounts[placement] = 0;
|
|
}
|
|
|
|
const sizes = (settings[config.sizes] || "").split("|");
|
|
|
|
if (sizes.length === 1) {
|
|
size = sizes[0];
|
|
} else {
|
|
size = sizes[renderCounts[placement] % sizes.length];
|
|
renderCounts[placement] += 1;
|
|
}
|
|
|
|
if (size === "fluid") {
|
|
return { width: "fluid", height: "fluid" };
|
|
}
|
|
|
|
const sizeObj = {
|
|
width: parseInt(splitWidthInt(size), 10),
|
|
height: parseInt(splitHeightInt(size), 10),
|
|
};
|
|
|
|
if (!isNaN(sizeObj.width) && !isNaN(sizeObj.height)) {
|
|
return sizeObj;
|
|
}
|
|
}
|
|
|
|
function defineSlot(
|
|
divId,
|
|
placement,
|
|
settings,
|
|
isMobile,
|
|
width,
|
|
height,
|
|
categoryTarget
|
|
) {
|
|
if (!settings.dfp_publisher_id) {
|
|
return;
|
|
}
|
|
|
|
if (ads[divId]) {
|
|
return ads[divId];
|
|
}
|
|
|
|
let ad, config, publisherId;
|
|
|
|
if (isMobile) {
|
|
publisherId = settings.dfp_publisher_id_mobile || settings.dfp_publisher_id;
|
|
config = MOBILE_SETTINGS[placement];
|
|
} else {
|
|
publisherId = settings.dfp_publisher_id;
|
|
config = DESKTOP_SETTINGS[placement];
|
|
}
|
|
|
|
ad = window.googletag.defineSlot(
|
|
"/" + publisherId + "/" + settings[config.code],
|
|
[width, height],
|
|
divId
|
|
);
|
|
|
|
custom_targeting(
|
|
keyParse(settings[config.targeting_keys]),
|
|
keyParse(settings[config.targeting_values]),
|
|
ad
|
|
);
|
|
|
|
if (categoryTarget) {
|
|
ad.setTargeting("discourse-category", categoryTarget);
|
|
}
|
|
|
|
ad.addService(window.googletag.pubads());
|
|
|
|
ads[divId] = { ad, width, height };
|
|
return ads[divId];
|
|
}
|
|
|
|
function destroySlot(divId) {
|
|
if (ads[divId] && window.googletag) {
|
|
window.googletag.destroySlots([ads[divId].ad]);
|
|
delete ads[divId];
|
|
}
|
|
}
|
|
|
|
function loadGoogle() {
|
|
/**
|
|
* Refer to this article for help:
|
|
* https://support.google.com/admanager/answer/4578089?hl=en
|
|
*/
|
|
|
|
if (_loaded) {
|
|
return RSVP.resolve();
|
|
}
|
|
|
|
if (_promise) {
|
|
return _promise;
|
|
}
|
|
|
|
// The boilerplate code
|
|
let dfpSrc =
|
|
("https:" === document.location.protocol ? "https:" : "http:") +
|
|
"//securepubads.g.doubleclick.net/tag/js/gpt.js";
|
|
_promise = loadScript(dfpSrc, { scriptTag: true }).then(function () {
|
|
_loaded = true;
|
|
if (window.googletag === undefined) {
|
|
// eslint-disable-next-line no-console
|
|
console.log("googletag is undefined!");
|
|
}
|
|
|
|
window.googletag.cmd.push(function () {
|
|
// Infinite scroll requires SRA:
|
|
window.googletag.pubads().enableSingleRequest();
|
|
|
|
// we always use refresh() to fetch the ads:
|
|
window.googletag.pubads().disableInitialLoad();
|
|
|
|
window.googletag.enableServices();
|
|
});
|
|
});
|
|
|
|
window.googletag = window.googletag || { cmd: [] };
|
|
|
|
return _promise;
|
|
}
|
|
|
|
export default AdComponent.extend({
|
|
classNameBindings: ["adUnitClass"],
|
|
classNames: ["google-dfp-ad"],
|
|
loadedGoogletag: false,
|
|
lastAdRefresh: null,
|
|
width: alias("size.width"),
|
|
height: alias("size.height"),
|
|
|
|
@discourseComputed
|
|
size() {
|
|
return getWidthAndHeight(
|
|
this.get("placement"),
|
|
this.siteSettings,
|
|
this.site.mobileView
|
|
);
|
|
},
|
|
|
|
@discourseComputed(
|
|
"siteSettings.dfp_publisher_id",
|
|
"siteSettings.dfp_publisher_id_mobile",
|
|
"site.mobileView"
|
|
)
|
|
publisherId(globalId, mobileId, isMobile) {
|
|
if (isMobile) {
|
|
return mobileId || globalId;
|
|
} else {
|
|
return globalId;
|
|
}
|
|
},
|
|
|
|
@discourseComputed("placement", "postNumber")
|
|
divId(placement, postNumber) {
|
|
let slotNum = getNextSlotNum();
|
|
if (postNumber) {
|
|
return `div-gpt-ad-${slotNum}-${placement}-${postNumber}`;
|
|
} else {
|
|
return `div-gpt-ad-${slotNum}-${placement}`;
|
|
}
|
|
},
|
|
|
|
@discourseComputed("placement", "showAd")
|
|
adUnitClass(placement, showAd) {
|
|
return showAd ? `dfp-ad-${placement}` : "";
|
|
},
|
|
|
|
@discourseComputed("width", "height")
|
|
adWrapperStyle(w, h) {
|
|
if (w !== "fluid") {
|
|
return htmlSafe(`width: ${w}px; height: ${h}px;`);
|
|
}
|
|
},
|
|
|
|
@discourseComputed("width")
|
|
adTitleStyleMobile(w) {
|
|
if (w !== "fluid") {
|
|
return htmlSafe(`width: ${w}px;`);
|
|
}
|
|
},
|
|
|
|
@discourseComputed(
|
|
"publisherId",
|
|
"showDfpAds",
|
|
"showToGroups",
|
|
"showAfterPost",
|
|
"showOnCurrentPage",
|
|
"size"
|
|
)
|
|
showAd(
|
|
publisherId,
|
|
showDfpAds,
|
|
showToGroups,
|
|
showAfterPost,
|
|
showOnCurrentPage,
|
|
size
|
|
) {
|
|
return (
|
|
publisherId &&
|
|
showDfpAds &&
|
|
showToGroups &&
|
|
showAfterPost &&
|
|
showOnCurrentPage &&
|
|
size
|
|
);
|
|
},
|
|
|
|
@discourseComputed
|
|
showDfpAds() {
|
|
if (!this.currentUser) {
|
|
return true;
|
|
}
|
|
|
|
return this.currentUser.show_dfp_ads;
|
|
},
|
|
|
|
@discourseComputed("postNumber")
|
|
showAfterPost(postNumber) {
|
|
if (!postNumber) {
|
|
return true;
|
|
}
|
|
|
|
return this.isNthPost(parseInt(this.siteSettings.dfp_nth_post_code, 10));
|
|
},
|
|
|
|
// 3 second delay between calls to refresh ads in a component.
|
|
// Ember often calls updated() more than once, and *sometimes*
|
|
// updated() is called after _initGoogleDFP().
|
|
shouldRefreshAd() {
|
|
const lastAdRefresh = this.get("lastAdRefresh");
|
|
if (!lastAdRefresh) {
|
|
return true;
|
|
}
|
|
return new Date() - lastAdRefresh > 3000;
|
|
},
|
|
|
|
@on("didUpdate")
|
|
updated() {
|
|
if (!this.shouldRefreshAd()) {
|
|
return;
|
|
}
|
|
|
|
let slot = ads[this.get("divId")];
|
|
if (!(slot && slot.ad)) {
|
|
return;
|
|
}
|
|
|
|
let ad = slot.ad,
|
|
categorySlug = this.get("currentCategorySlug");
|
|
|
|
if (this.get("loadedGoogletag")) {
|
|
this.set("lastAdRefresh", new Date());
|
|
window.googletag.cmd.push(() => {
|
|
ad.setTargeting("discourse-category", categorySlug || "0");
|
|
window.googletag.pubads().refresh([ad]);
|
|
});
|
|
}
|
|
},
|
|
|
|
@on("didInsertElement")
|
|
_initGoogleDFP() {
|
|
if (isTesting()) {
|
|
return; // Don't load external JS during tests
|
|
}
|
|
|
|
if (!this.get("showAd")) {
|
|
return;
|
|
}
|
|
|
|
loadGoogle().then(() => {
|
|
this.set("loadedGoogletag", true);
|
|
this.set("lastAdRefresh", new Date());
|
|
|
|
window.googletag.cmd.push(() => {
|
|
let slot = defineSlot(
|
|
this.get("divId"),
|
|
this.get("placement"),
|
|
this.siteSettings,
|
|
this.site.mobileView,
|
|
this.get("width"),
|
|
this.get("height"),
|
|
this.get("currentCategorySlug") || "0"
|
|
);
|
|
if (slot && slot.ad) {
|
|
// Display has to be called before refresh
|
|
// and after the slot div is in the page.
|
|
window.googletag.display(this.get("divId"));
|
|
window.googletag.pubads().refresh([slot.ad]);
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
willRender() {
|
|
this._super(...arguments);
|
|
|
|
if (!this.get("showAd")) {
|
|
return;
|
|
}
|
|
},
|
|
|
|
@on("willDestroyElement")
|
|
cleanup() {
|
|
destroySlot(this.get("divId"));
|
|
},
|
|
});
|