Jump to content

nbcommunication

Members
  • Posts

    286
  • Joined

  • Last visited

  • Days Won

    7

Everything posted by nbcommunication

  1. Hi, This module does not appear to be compatible with the latest master. Seems the refactoring of InputfieldDatetime since 3.0.148 is the culprit. Failed to construct module: \InputfieldDatetimeAdvanced - Method InputfieldDatetimeAdvanced::getInputFormats does not exist or is not callable in this context I added getInputFormats() from a previous version of InputfieldDatetime and that appears to have fixed it: <?php /** * Get the input format string for the user's language * * thanks to @oliverwehn (#1463) * * @param bool $getString Specify true to get a format string rather than an array * @return array|string of dateInputFormat timeInputFormat * */ protected function getInputFormats($getString = false) { $inputFormats = array(); $language = $this->wire('user')->language; $useLanguages = $this->wire('languages') && $language && !$language->isDefault(); foreach(array('date', 'time') as $type) { $inputFormat = ''; if($useLanguages) { $inputFormat = trim($this->getSetting("{$type}InputFormat{$language->id}")); } if(!strlen($inputFormat)) { // fallback to default language $inputFormat = $this->get("{$type}InputFormat"); } $inputFormats[] = $inputFormat; } if($getString) return trim(implode(' ', $inputFormats)); return $inputFormats; } Cheers, Chris
  2. Render oEmbed data from YouTube/Vimeo URLs... or TextformatterVideoEmbed for power users. https://github.com/nbcommunication/TextformatterVideoMarkup The use case... On an upcoming project, we want to be able to render YouTube/Vimeo URLs as thumbnail images, that when clicked open up in a (UIkit) lightbox. Additionally, we want to be able to specify the thumbnail image - as part of a RepeaterMatrix block which contains a URL field (video) and an Image field (thumb). The result is this module, which allows you to specify the markup used to render the oEmbed data: The formatter can be used on any Text field e.g. Text, Textarea (CKEditor or not), URL etc. Global configuration options are available (e.g. rel=0), based on TextformatterVideoEmbedOptions. An 'empty value' can be specified for URLs that do not return data from the oEmbed endpoint The render method is hookable, allowing you to customise rendering on a per page, per field basis Plenty more information here ? https://github.com/nbcommunication/TextformatterVideoMarkup/blob/master/README.md Back to the use case... How do we render the thumbnail and then use the image from our Image field? In the module config Markup field: <figure data-uk-lightbox> <a href="{url}" data-poster="{thumbnail_url}" data-attrs="width: {width}; height: {height}"> <img src="{thumbnail_url}" alt="{title}"> </a> </figure> Then in site/ready.php <?php $wire->addHookBefore('TextformatterVideoMarkup::render', function(HookEvent $event) { // Arguments (for info) $tpl = $event->arguments(0); // string: The markup template $data = $event->arguments(1); // array: The oEmbed data $url = $event->arguments(2); // string: The requested URL $emptyValue = $event->arguments(3); // string: The empty value used if no data is returned // Object properties (for info) $page = $event->object->page; // Page: The page $field = $event->object->field; // Field: The field $html = $event->object->html; // bool: Is it HTML being parsed, or plain text? // Replace the thumbnail image if($field->name == 'video' && $page->hasField('thumb') && $page->thumb) { $data['thumbnail_url'] = $page->thumb->url; $event->arguments(1, $data); } }); The module requires PW >= 3.0.148 and PHP >= 7. It probably doesn't need to, but the expectation is that power users will be able to meet these requirements! The module is also Beta - please don't use in production yet. I suspect there will be edge cases related to the changes I made to the URL regexes from TextformatterVideoEmbed - so far though they are working for me. If you come across any issues please let me know! Cheers, Chris
  3. That didn't take long - @kkalgidim, if you re-download (should now be version 1.4.1) then hopefully that'll work for you.
  4. @kkalgidim - looks like PHP < 5.6. Arrays in class constants are allowed from 5.6 onwards. You really should be on 7.2 or above! Given that ProcessWire supports 5.4 and above, I'll look into tweaking this so you can install. Cheers, Chris
  5. Hi @kkalgidim, What PHP version are you running? Cheers, Chris
  6. Updated to 1.0.1 (Stable), mainly reducing hook priority < 200 so it runs before ProCache.
  7. Hi @jonatan, You would probably need to do something like: var resetPagination = new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open("GET", window.location.href); oReq.addEventListener(xhr, "load", function() { if (xhr.status === 0 || xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { resolve(xhr); } else { reject(); } }); oReq.addEventListener(xhr, "error", function() { reject(); }); oReq.addEventListener(xhr, "timeout", function() { reject() }); xhr.send(); }); resetPagination.then(function() { instagram.init(); }, function() { // Error }); If I remember correctly, this kind of plain Javascript request doesn't actually register as an ajax request, so $config->ajax returns false, and the pagination will be reset. I'm not sure this is the best way to go though - I'd probably be trying to store the requested data in an array/object and then just pull it from there when the page changes e.g. var requests = {}, var items = []; // On page change var page = "page" + pageNum; // whereever you get this info from if(page in requests) { items = requests[page]; } else { items = "an AJAX request to get the next page of items"; requests[page] = items; } It will probably require a bit of reworking of your code, but it's a far better way to go. You shouldn't have to request the data more than once - store it! Hope that helps, Chris
  8. Hi, Update to 1.3.3 - tweak to how the cache name/key is generated, shouldn't effect anything... Cheers, Chris
  9. Hi @jonatan, Nice one! No thanks required - you've highlighted issues which has made the module significantly better! Cheers, Chris
  10. Hi @LAPS, I've added a setCampaign() method. It is essentially an alias for addTags(), but takes multiple string arguments instead of an array like addTags(). <?php $mg->to("an@email.com") ->subject"A Subject") ->setCampaign("campaign1", "campaign1-a", "testing") ->bodyHTML("<p>A Message</p>") ->send(); It also enables open and click tracking by default, as this is presumably behaviour you want for campaign analytics. If this isn't required, using addTags() is the way to go, but you can also pass false as the first argument to disable this behaviour and use whatever tracking settings you have by default. Cheers, Chris
  11. Hi @LAPS, OK - I'll take a look at this tomorrow and implement a method for this. Cheers, Chris
  12. Hi @LAPS, Campaigns isn't something I have experience with, but is it not a reporting system in Mailgun for analysing/comparing messages? From what I can gather, this uses message data and/or tags to do this, both of which are already implemented in the module. There isn't anything available in the API specifically related to campaigns. Is there another implementation you'd like to see, something like a setCampaign() method which adds the message data? Cheers, Chris
  13. The API issue with account_type has been resolved, so I've reinstated this in the module, version 1.3.2.
  14. Hi @jonatan, I'm not sure what more I'd be able to do to directly help here. At this point your best bet is to add a bunch of logging to try and determine where the problem is occurring. For example: // Attach listener console.log("Should only log once on init"); $(window).scroll(this.debounce(function() { console.log("Probably not needed, this will log during scrolling"); if(!this$1.busy && $(window).scrollTop() == $(document).height() - $(window).height()) { console.log("User has reached the bottom."); // When this is logged I should really only see one at the time due to the debounce function and the 'busy' switch. this$1.get(); // Similar logging to be added to get() } }, 256)); <?php $instagram = $modules->get("InstagramBasicDisplayApi"); // The next url is stored in session. The code below will log it. $instagram->log($session->getFor($instagram, "next$page->id")); if($config->ajax) { $instagram->log("You may want to log here to test how often AJAX requests are made"); header("Content-Type: application/json"); echo $instagram->getMedia(); // ["json" => true] is inferred by $config->ajax die(); } By using logging to trace the process, you will hopefully be able to figure out what is causing the issue. I suspect it is still the listener. Cheers, Chris
  15. Hi @houseofdeadleg, Seems to be an issue on the APIs end with the "account_type" field. There's an open bug report: https://developers.facebook.com/support/bugs/1127960260870980/ I've updated the module to remove this field for the time being. If you update, it should work for you. Cheers, Chris
  16. Hi @jonatan, Aye it is the listener. Try the following: var instagram = { $el: {}, // Where the items go $loading: {}, // The loading spinner total: 0, // The total number of items busy: false, init: function() { this.$el = $("#instagram"); if(!this.$el.length) return; // Add the spinner this.$el.after("<div id='instagram-loading'><span class='loadertextcontainer'><b class='loadertext'>loading<span>_</span><span>_</span><span>_</span></b></span></div>"); this.$loading = $("#instagram-loading"); this.$loading.hide(); // Get the first batch of items this.get(); var this$1 = this; // Attach listener $(window).scroll(this.debounce(function() { if(!this$1.busy && $(window).scrollTop() == $(document).height() - $(window).height()) { this$1.get(); } }, 256)); }, debounce: function(func, wait, immediate) { var timeout; if(wait === void 0) wait = 256; return function() { var context = this; var args = arguments; var later = function() { timeout = null; if(!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if(callNow) func.apply(context, args); }; }, get: function() { var this$1 = this; // Show spinner this$1.$loading.show(); // Prevent requests while this one is running this$1.busy = true; // Request $.getJSON(window.location.href, function(data) { // Hide spinner this$1.$loading.hide(); if(!$.isArray(data) || !data.length) return; // If no items do not render var items = []; data.forEach(function(item, index) { switch(item.type) { case "VIDEO": items.push(this$1.renderItemVideo(item.src, item.alt, item.href, index, item.poster)); break; case "CAROUSEL_ALBUM": var out = ""; for(var i = 0; i < item.children.length; i++) { var src = item.children[i].src; if (i <= 0) { out += "<a class='carousel-item active fb-gallery-ig' data-height='600' data-caption='" + item.href + "' href='" + src + "'>" + "<div style='background-image: url(" + src + ")' class='pics'></div>" } else { out += "<a class='carousel-item fb-gallery-ig' data-height='600' data-caption='" + item.href + "' href='" + src + "'>" + "<div style='background-image: url(" + src + ")' class='pics'></div>" } +"</a>"; } items.push( "<div data-interval='3000' class='carousel slide carousel-fade col-12 col-sm-6 col-md-4 p-3 pics' data-ride='carousel' id='instagram-item-" + (this$1.total + index) + "'>" + "<div class='carousel-inner'>" + out + "<a class='carousel-control-prev' href='#instagram-item-" + (this$1.total + index) + "' role='button' data-slide='prev'>" + "<span class='carousel-control-icon-bg' aria-hidden='true'></span>" + "<span class='carousel-control-prev-icon' aria-hidden='true'></span>" + "<span class='sr-only'>Previous</span>" + "</a>" + "<a class='carousel-control-next' href='#instagram-item-" + (this$1.total + index) + "' role='button' data-slide='next'>" + "<span class='carousel-control-icon-bg' aria-hidden='true'></span>" + "<span class='carousel-control-next-icon' aria-hidden='true'></span>" + "<span class='sr-only'>Next</span>" + "</a>" + "</div>" + "</div>" ); break; default: // IMAGE items.push(this$1.renderItem(item.src, item.alt, item.href, index)); break; } }); var count = items.length; if(count) { // Append items to the container this$1.$el.append(items.join("")); // Allow more requests this$1.busy = false; // Update total this$1.total = this$1.total + count; } }, function(e) { this$1.$loading.hide(); console.log(e); // ERROR }) }, renderItem: function(src, alt, href, index, poster) { return "<div class='col-12 col-sm-6 col-md-4 p-3 pics' id='instagram-item-" + (this.total + index) + "'>" + "<a class='fb-gallery-ig' data-height='600' data-caption='" + href + "' href='" + src + "' data-alt='" + alt + "'>" + "<div style='background-image: url(" + src + ")' class='pics'>" + "</div>" + "</a>" + "</div>"; }, renderItemVideo: function(src, alt, href, index, poster) { return "<div class='col-12 col-sm-6 col-md-4 p-3 pics' id='instagram-item-" + (this.total + index) + "'>" + "<a class='fb-gallery-ig' data-height='600' data-caption='" + href + "' href='" + src + "'>" + "<div style='background-image: url(" + poster + ")' class='pics'>" + (src !== poster ? "<div class='overlay-video-icon'></div>" : "") + "</div>" + "</a>" + "</div>"; } }; $(document).ready(function() { instagram.init(); }); I've added a "busy" variable which prevents requests when one is already being made, and a debounce function to control the scroll handler which has been moved to init(). Works for me. Cheers, Chris
  17. Hi @jonatan, It is probably related to the listener - when I get a chance I'll fire up the code and see if I can figure out a solution. Cheers, Chris
  18. Nice one. Yeah, my implementation using UIkit's Scrollspy component isn't great to be honest, but I didn't want to use a 3rd-party plugin for the example. Your best bet would be to look for a jQuery plugin for handling the scroll-to-bottom thing, I'm sure something will exist! Basically, when the scroll-to-bottom is triggered, you need to call the get() function. This should replace the // Attach Listener part of my example. Cheers, Chris
  19. Hi @jonatan, Try putting the if($config->ajax) code at the top of your template file, before anything else is being output. This will ensure that what is being returned is purely JSON. You can log failed requests like so: $.getJSON(window.location.href, function(data) { // ... }).fail(function(jqXHR, textStatus, errorThrown) { console.log(jqXHR, textStatus, errorThrown); }); I think what you'd probably be getting is a JSON parse error due to the html being output prior to the AJAX response. The reason for the next results to not be explicit in the way you've suggested is that the "next" link provided by the API is tied to the original request, and since pagination is likely to be handled by AJAX requests, it makes sense to just build this in as I've done. Cheers, Chris
  20. Hi @jonatan, I've been able to pick away at this over the weekend... too cold yet to plant tatties! With the discovery of the hidden limit option in the API, I had to rewrite some of the module, but everything should just work the same, if not even better... However, I'm not sure it'll help too much with API rate limiting in your case, as each carousel album is still a separate API call. There is now the option to prevent the "children" (the rest of the album carousel images) from being retrieved: <?php $instagram = $modules->get("InstagramBasicDisplayApi"); $items = $instagram->getMedia(["children" => false]); When working with this over the weekend, I kept an eye on the rate limiting page and it did get nudged frequently, but didn't go beyond 30% used. The account I was using for testing only had a few carousels though. I've also been able to implement "lazy loading". The example I've added to the README is based on the getMedia() example, using UIkit's javascript utilities. I've attached a short clip of how this looks. Here's how it might work using Bootstrap/jQuery: In your template file: <?php $instagram = $modules->get("InstagramBasicDisplayApi"); // The pagination cursor is reset if the request is not AJAX, e.g. when the page is loaded. if($config->ajax) { header("Content-Type: application/json"); echo $instagram->getMedia(); // ["json" => true] is inferred by $config->ajax die(); } echo "<div id='instagram' class='row'></div>"; In your javascript: // Totally untested!!!! var instagram = { $el: {}, // Where the items go $loading: {}, // The loading spinner total: 0, // The total number of items init: function() { this.$el = $("#instagram"); if(!this.$el.length) return; // Add the spinner this.$el.after("<div id='instagram-loading'>...Your choice of spinner!</div>"); this.$loading = $("#instagram-loading"); this.$loading.hide(); // Get the first batch of items this.get(); }, get: function() { var this$1 = this; // Show spinner this$1.$loading.show(); // Request $.getJSON(window.location.href, function(data) { // Hide spinner this$1.$loading.hide(); if(!$.isArray(data) || !data.length) return; // If no items do not render var items = []; data.forEach(function(item, index) { switch(item.type) { case "VIDEO": items.push(this$1.renderItem(item.poster, item.alt, item.src, index)); break; case "CAROUSEL_ALBUM": var out = ""; for(var i = 0; i < item.children.length; i++) { var src = item.children[i].src; out += "<a class='carousel-item' data-fancybox='gallery-ig' data-height='600' data-caption='" + item.alt + "' href='" + src + "'>" + "<div style='background-image: url(" + src + ")' class='pics'></div>" + "</a>"; } items.push( "<div data-interval='3000' class='carousel slide carousel-fade col-4 p-3 pics' data-ride='carousel' id='instagram-item-" + (this$1.total + index) + "'>" + "<div class='carousel-inner'>" + out + "<a class='carousel-control-prev' href='#_{$post->id}' role='button' data-slide='prev'>" + "<span class='carousel-control-icon-bg' aria-hidden='true'></span>" + "<span class='carousel-control-prev-icon' aria-hidden='true'></span>" + "<span class='sr-only'>Previous</span>" + "</a>" + "<a class='carousel-control-next' href='#_{$post->id}' role='button' data-slide='next'>" + "<span class='carousel-control-icon-bg' aria-hidden='true'></span>" + "<span class='carousel-control-next-icon' aria-hidden='true'></span>" + "<span class='sr-only'>Next</span>" + "</a>" + "</div>" + "</div>" ); break; default: // IMAGE items.push(this$1.renderItem(item.src, item.alt, item.src, index)); break; } }); var count = items.length; if(count) { // Append items to the container this$1.$el.append(items.join("")); // Attach listener // No idea if this works!!! $(window).on("resize scroll", function() { var next = $("#instagram-item-" + (this$1.total + count - (count < 5 ? 1 : 6))); if(!next.hasClass("seen") && this$1.inViewport()) { next.addClass("seen"); this$1.get(); } }); // Update total this$1.total = this$1.total + count; } }, function(e) { this$1.$loading.hide(); console.log(e); // ERROR }) }, inViewport: function(el) { // Untested, got the code from // https://medium.com/talk-like/detecting-if-an-element-is-in-the-viewport-jquery-a6a4405a3ea2 var elementTop = el.offset().top; var elementBottom = elementTop + el.outerHeight(); var viewportTop = $(window).scrollTop(); var viewportBottom = viewportTop + $(window).height(); return elementBottom > viewportTop && elementTop < viewportBottom; }, renderItem: function(src, alt, href, index) { return "<div class='col-4 p-3 pics' id='instagram-item-" + (this.total + index) + "'>" + "<a data-fancybox='gallery-ig' data-height='600' data-caption='" + alt + "' href='" + href + "'>" + "<div style='background-image: url(" + src + ")' class='pics'>" + (src !== href ? "<div class='overlay-video-icon'></div>" : "") + "</div>" + "</a>" + "</div>"; } }; $(document).ready(function() { instagram.init(); }); I've just done the jQuery from memory with the addition of an inViewport function I found online. No idea if it works! Let me know how you get on! Cheers, Chris instagram-basic-display-api-lazy-2020-03-29.mp4
  21. Hi @jonatan, I'm making good progress on this but not finished yet. Discovered that there's a hidden "limit" API option which has necessitated a bit of a rewrite. May get to it this weekend (weather is looking good though, so might be out in the garden!), if not on Monday. Cheers, Chris
  22. Hi @jonatan, Aye lazy loading is proving to be a little tricky, mainly because of caching. I need to think through this more and will get back to it tomorrow. Cheers, Chris
  23. Hi @jonatan, I've managed to get access to an account with 20 items for testing. The "counting" in the module is actually correct, I think the issue is maybe that after the limit is reached the API just returns blank records? Anyway, I've added a little bit of code that removes any items without a media_url before they are returned, which should fix your issue. Code not pushed yet, should be later today. Cheers, Chris
  24. Hi @jonatan, Thanks for the debug array. Yep there's definitely an issue with asking for more items than there actually is. Will get that sorted. Each carousel requires a separate API call. That means each request for 80 items (that isn't cached) uses up 50 API calls. The error isn't at that point. Try this: <?php switch($post->type) { case "VIDEO": // ... break; case "CAROUSEL_ALBUM": // If the post does not have children, continue without rendering if(!$post->children) break; // ... break; // ... } Cheers, Chris
  25. Hi @jonatan, No, I think echoing as you are doing would probably be faster if anything. Basically no difference - I just like to use the array so I don't have to wrap with <div></div> in three separate places. I'm going to look into making the pagination more available through the module tomorrow. Might be tricky, and it probably will need to be batches of 24, but I definitely think you've made a good case that it should be available. I think that may have been from the previous API. This one returns the first 24 items, and a link for getting the next 24. You can keep going on until all items have been received or until the API limits have been maxed out. Indeed, this is what would happen if you were to call something like InstagramBasicDisplayApi::getMedia(10000), although I think there would be a timeout before completion. If you request 89 and there are only 80, it should return 80, but I don't think I actually tested that. I don't have access to an account at the moment with a relatively small amount of items, but should do soon, so will test this when I can. I suspect there is an issue and this is why you are getting the undefined offset errors and the blank items. For debugging, I'd add the following to your code: <?php $images = $instagram->getMedia(89); echo count($images) . "<br>" . print_r($images, 1); If you are requesting 80 items, and 10 of those are album carousels, that should be 14 calls to the API (24, 24, 24, 24[8] + a call for each carousel). How many carousels do you have? When I was developing and testing I didn't budge the limit off 0%. Will have another look at this tomorrow. I think it may be useful to have an option to not get the full carousel when using getMedia() as this does seem to be the issue. I'm not following this. I think I know what you mean now I've gone through the following... I'll tweak your code to what I think you are trying to achieve and that'll maybe get us closer: <?php $username = "a_username"; $account = $instagram->getProfile($username); $postsnum = $account["media_count"] ?? 0; // If the API request is unsuccessful, the post count below will not be displayed ?> <div class="col-12 text-center mb-3"> <a href="https://instagram.com/<?= $username ?>/" target="_blank" class="marked"> <h3 class="marked my-5 instalink">@<?= $username ?></h3> </a> <?php if($postsnum): ?><h5 class="marked-dark"><?= $postsnum ?> POSTS / OPSLAG </h5><?php endif; ?> </div> getProfile() is a single call which will be cached for either an hour or however long you have the cache time set to so I wouldn't consider this a resource issue. Given that the cache time will be the same for getMedia(), the count and the number of items you get if you were to get them all should be the same, but the count won't update until the cache has expired. getUserAccount() should have the same value for media_count, but it is only updated when getProfile() is called (and an API request is made) so I don't think it is worth using in this situation. It is a public method but only so it could be used in the config - I can't think of a situation where it should be used over getProfile(). The errors you've pointed out in your last post are related to the total number issue which I'll get fixed as soon as I can. I'll hopefully have some progress on this tomorrow! Thanks again for your feedback! Cheers, Chris
×
×
  • Create New...