Skip navigation
All Places > Developer > Blog > Author: Michael Shaheen

Developer

7 Posts authored by: Michael Shaheen Employee

With more and more customers utilizing SugarCloud products, I thought it would be a good idea to enumerate some basic best practices when developing for SugarCloud. As Sugar's cloud-based product line evolves, I will add more items to this list.

 

When developing for SugarCloud:
Don't

use custom code when configuration will do just fine.

The ability to write custom code for Sugar is a huge benefit. It isn't, however, the best solution for all situations. Very often your problem can be alleviated by simply using the configuration tools that Sugar provides in its admin console. Manipulating a configuration in the system is typically a safer choice as there is no concern with upgrade compatibility.

Don't

have direct filesystem or DB access.

SugarCloud is a shared environment. Any changes made to the filesystem could impact other customers.

Don't

use blacklisted classes, functions, or file types.

In order to maintain the integrity of the standard Sugar functionality when we upgrade a customer instance and limit any negative impact our upgrade has on the customer's modifications, all instances hosted on Sugar's cloud service have package scanner enabled. Here is a blacklist of cases that will cause the package scanner to fail.

Don't

perform load or pen testing without permission of Sugar Support.

SugarCloud is a shared environment. An unscheduled load test will may cause performance issues with other customers' instances. You must obtain Sugar Support's permission so that they may make the proper adjustments to ensure no other instances are affected by your tests.

Don't

introduce performance or security issues with your code.

For the safety and security of your users, it is never wise to introduce performance or security issues into your code. This is especially true when working in a shared environment so as not to affect other customers' user experiences.

Don't

disable or circumvent package scanner.

Package scanner is enabled on all cloud instances to ensure no security violations are introduced.

Don't

allow an outbound HTTP connection to last longer than 1 second.

SugarCloud is a shared environment. Long connections can have a performance impact on your users as well as the users of other customers.

Don't

abuse the job queue with a multitude of long running jobs.

SugarCloud is a shared environment. Long running can have a performance impact on your users as well as the users of other customers. If you load the queue with too many long running jobs, the rest of the jobs awaiting their turn will be affected

Don't

abuse the REST API with more than 20 requests per second.

SugarCloud is a shared environment. Too many requests can have a performance impact on your users as well as the users of other customers.

Do

upgrade to every new release.

Sugar Sell and Sugar Serve operate on a quarterly update cycle while Sugar Market is updated approximately every two weeks. Each update will include new improvements or fixes from the previous version. It is important to keep up-to-date on these upgrades to minimize the number of things that will need to be tested. 

Do

test before you deploy!

It is always better to find any issues in a test environment prior to deploying live. If there are issues or incompatibilities after a change, these should be caught and addressed before a user runs into a problem.

In response to the recent evolution of the SugarCRM product line, we’ve compiled a list of answers to some common questions that we have received from the developer community about our new products. This FAQ will be a living document, so please post any additional questions in the comments section and we will do our best to address them here.

Sugar Professional and Enterprise customers: 

If you are an existing customer of Sugar Professional or Sugar Enterprise then nothing has changed for you. If you are in our cloud, you will still get new features on a quarterly basis. If you are on-premise, you will still get new features on an annual basis.

  
QuestionSugar MarketSugar SellSugar Serve
Will it be available On-Premise?No. Sugar Market, Sugar Sell, and Sugar Serve are available via SaaS only and are not available for on-site deployment.
Can we write code customizations for it?The Sugar Market platform does not support direct code customizations. It does, however, have REST APIs and other tools to enhance your development.

Yes, Sugar Sell and Sugar Serve are based on the Sugar Enterprise platform which supports code customizations. You can use Module Loader to install code customizations for Sugar Serve and Sell. Since Serve and Sell are built on Sugar Enterprise, use the ENT flavor in your package manifests.

How can we download instance backups for local development and test?No, Sugar Market is a multi-tenant application. There is no concept of local development.Yes using Backups module.
Can we get data backup?Yes, by exporting a report.Yes using Backups module.
Can we access development builds?We are working toward a solution for this.There is a Developer Builds space in the SugarCRM Developers community. We will post development builds here for each release.
Will Sell/Serve/Market use the same platform as Sugar Enterprise?Sugar Market is a unique platform.Sugar Sell and Sugar Serve are built on the Sugar Enterprise platform.
Will Sell/Serve/Market be connected to the same database, or will they be separate instances connected via API?At this time, Sugar Market utilizes an independent database. Sugar Market integrates with Sugar Sell/Ent/Pro out of the box.A single SugarCloud instance and database can have both users of Sugar Sell and Sugar Serve using it at the same time.
Where can I find more specific info about the divergence between Sugar products?The differences between the SugarCRM License Types are outlined in the User Management section of the Sugar Enterprise 9.1 Administration Guide.
What resources are available if I have more questions?Sugar Market DocumentationSugar Sell DocumentationSugar Serve Documentation
How often will Sugar products be updated?

Sugar Market operates on a continuous update cycle, with releases approximately every two weeks.

Sugar Sell and Sugar Serve operate on a quarterly release cycle (every three months).

Sugar Summer '19 release is officially live! There's a huge buzz around our offices for this release - it's very exciting! Summer '19 introduces brand new Sugar products as well as many enhancements for existing Sugar Professional and Sugar Enterprise customers.

 

We recently held a webinar with an overview of what is in the Summer '19 release. If you missed it, watch the recording here or view the slides from this post. Note: Sugar 9.1 (Summer '19) is a cloud-only Sugar release. On-premise customers must wait until Sugar 10.0 (Spring '20) to obtain the feature enhancements provided in this release.

 

Here's the TL;DR for those of you looking for a quick list:

 

  • SugarIdentity service is out of beta. SugarIdentity is a set of user authentication and access management microservices that will improve how we manage Sugar cloud users today. It offers improved OAuth 2.0 support, leverages OpenID Connect, and supports SAML Web Single Sign On (SSO) with the MS Outlook Plug-In. Read more about SugarIdentity in our post called What you need to know about the new SugarIdentity service! All new SugarCloud customers in the Americas, including those customers of our new Sugar Sell and Sugar Serve products, are using SugarIdentity today.
  • Sugar products now support TLS encryption for LDAP single sign-on.
  • A new field on user records, License Type, has been added to allow administrators to grant each user access to one or more products including SugarCRM's newest offerings, Sugar Sell and Sugar Serve.
  • NEW PRODUCTS!! We are very excited to announce 3 new products:
    • Sugar Market is a rebranding of Sugar's recently-acquired marketing automation solution, SalesFusion.
    • Sugar Sell is our award-wining sales automation solution.
    • Sugar Serve is Sugar's new customer engagement center solution.
  • The new SugarCloud Insights page allows administrators to easily monitor their instance's database and file system storage usage, license usage, as well as gain access to PHP error logs and access logs.
  • Shareable dashboards now include custom user-created filters.
  • Tile View : For cases, tasks, and opportunities, a new view has been added that displays records as tiles in a familiar interactive, drag-and-drop interface.
  • Bug and Case bean classes now extend \Issue instead of \Basic class.
  • A new direction field has been added to the Emails module. The possible values are inbound, outbound, internal, unknown.

 

For details on everything in this release, check out our other Summer '19 resources:

We intend to disable support for TLS v1.1 and older in the SugarCloud. This action is consistent with the rest of the industry. It may impact some Sugar integrations that connect to the SugarCloud. If you are hosting Sugar on-site, you should consider taking similar steps to disable TLS v1.1 and earlier on your web servers.

 

Read on to learn more.

TLS/SSL Vulnerabilities

The SSL (“Secure Sockets Layer”) protocol was initially invented by Netscape back in the mid-1990s as a method for securing communications over a computer network. This protocol provides the “S” in HTTPS which is used to secure all HTTP traffic to Sugar web servers. As you might expect with 25 year old security technology, there’s been quite a few revisions and improvements to the original concept over time. In fact, SSL v3.0 came out in 1996 which was only a couple years after SSL itself was first invented. SSL was later succeeded by TLS (“Transport Layer Security”) which itself has seen several iterations.

 

Protocol

Published

Status

SSL 1.0

Unpublished

Unpublished

SSL 2.0

1995

Deprecated in 2011 (RFC 6176)

SSL 3.0

1996

Deprecated in 2015 (RFC 7568)

TLS 1.0

1999

Deprecation planned in 2020

TLS 1.1

2006

Deprecation planned in 2020

TLS 1.2

2008

TLS 1.3

2018

Courtesy of Wikipedia

 

With most technology, the penalty for not adopting the latest and greatest is mostly FOMO (“fear of missing out”). But cryptographic protocols are used for target practice by white and black hat wearing security researchers the world over. This means that using out of date cryptographic protocol compounds FOMO with FOLE (“fear of losing everything”).

 

The value of a TLS/SSL protocol is inversely proportional to the number of holes that have been punched into it. Some of these holes are exploits that go by the name of POODLE and BEAST. At the same time, the industry has been continuously adding better and stronger encryption protocols in response.

 

The industry is dropping support of old TLS versions

SSL is REALLY old, so hopefully nobody is still using this. However, there is still plenty of code out there using older versions of TLS. The PCI Data Security Standard requires all connections to use TLS v1.1 or higher while strongly recommending TLS v1.2 or higher. Even the browser vendors who are loathe to drop features that could impact website compatibility (and market share) have agreed to drop support for TLS v1.0 and v1.1 in 2020.

 

As a result, we are considering the right time to disable support for TLS v1.1 and older for connections to the SugarCloud. This may impact some Sugar integrations that connect to the SugarCloud as we look to stay in step with the rest of the industry.

 

Make sure your REST API integrations are using TLS v1.2+

If you are using a modern web browser, then it is unlikely that you will run into any problems connecting to Sugar instances. However, some REST API integrations that are using old client libraries or runtimes are liable to use these older protocols. Basically, if you are running 10+ year old software in your integration then you will likely have some of these problems below.

 

In particular, please take extra care if you are using any of the following technology with your Sugar integration.

 

Client

Preferred Runtime

Apache HttpComponents

Use latest Java 8 or greater

RestSharp

Use latest .NET 4.7 or greater

cURL and OpenSSL (PHP)

Use OpenSSL 1.0.x or greater (PHP 7.1 or greater)

 

If you aren’t sure, you can use a network analyzer to verify the version of TLS that is in use. For example, you can use tcpdump or Wireshark.

 

Take the following steps if you believe your integration is affected.

  • If applicable, upgrade to newer runtime environments for your integrations
    • Ex. Upgrade to Java 8 or newer or to .NET 4.6 or newer
  • Upgrade to latest HTTP client library versions
    • Ex. HttpComponents v4.4.11+ is compatible with TLS v1.3 implementation found in Java 11
  • Configure your HTTP clients to require use of TLS v1.2

How to disable TLS v1.1 and earlier for Sugar on-site installations

You will typically configure the web server with the versions of TLS/SSL that will be allowed by your Sugar instance.

 

For Apache, the allowed versions of TLS can be configured using mod_ssl’s SSLProtocol directive

 

For IIS, the allowed versions of TLS can be configured using TLS Registry Settings.

 

Connections using TLS v1.1 or earlier will break

Only 6% of web traffic in SugarCloud is using an out of date version of TLS. So we are moving aggressively to ensure SugarCloud will only support TLS v1.2+ in the future. 

 

We will provide more updates as we build a timeline for making this change.

Hello Sugar Developers!

 

We often get questions about building code customizations for SugarCloud. Even experienced developers who have developed customizations for on-site Sugar installations do not know what is possible in SugarCloud. In short, Sugar Cloud does support custom code though there are some rules that need to be followed. We will dig into how write code customizations for SugarCloud in this webinar.

 

What we will be covering:

We will demonstrate by example (with the help of Professor M) the following topics:

  • Accessing SugarCloud Developer Builds
  • How to configure local dev environment to be similar to SugarCloud
  • Methods for managing and deploying custom code in SugarCloud
  • How to debug problems in a Cloud instance
  • Important rules to keep in mind

 

Webinar Information:

Join us for the live webinar:

Tuesday, July 16th 7:00 - 8:00 AM PT

Register Now!

 

Can’t make it? Don’t worry! We will be posting the webinar recording to this community for those who are unable to attend the live session.

SugarCRM strives to be the "No-Touch" CRMOne of the most important steps in making this vision a reality is to integrate Sugar with systems that customers uses to engage with organizations. For example, if a customer has been recently complaining about your product on your blog, wouldn't you want to know about that before talking with them?

 

Building a Wordpress integration

 

This post will explain how to import a comment from your blog (using Wordpress for this example) into Sugar as a note record related to the lead or contact who posted it. The goal of this exercise was to illustrate how to communicate from a common external content management system and Sugar. It turns out that getting the data into Sugar using REST APIs is pretty simple. It was everything else that took some thought and time.

 

The project started out as "send a Wordpress comment to Sugar as a note." It turned into much more than that (and has the potential to go so much further). So, here's a high level of our steps:

 

  • Created a Wordpress plugin
    • Plugin will record the Sugar login credentials and instance path
    • Plugin will call the Sugar OAuth REST endpoint to validate the user's credentials before allowing a save 
  • Upon submitting a comment to any Wordpress blog post, the comment data is sent to our instance of Sugar as a Note
    • Get the current logged-in Wordpress user
    • Authenticate to the Sugar API
    • Check if Wordpress user is in Sugar already (by e-mail address)
      • If so, add this comment as a Note linked to their [Account] record
      • If not, create a Lead record then create and link a Note to that record

 

Every CMS has its own special methods for connecting to external APIs. I chose to not use those functions in Wordpress for this exercise and simply used PHP's cURL functions. This should allow our example to be reused more easily with other applications.

 

The first step was to create the Wordpress plugin with a Settings page to allow a Wordpress admin to configure the integration. Our Settings page asks for Sugar user credentials, the URL to your instance of Sugar, and which version of the API your instance is using. All of this information gets stored in the Wordpress database for easy retrieval by our plugin.

 

The Wordpress settings page for our custom plugin

 

Now that we have our credentials, we will do something with them. We will create some functions that kick off when a Wordpress user adds a comment on any blog post. Wordpress has an action called "comment_post" that gets triggered after a comment is added by a user and subsequently stored in Wordpress database. We can assign a custom function to fire when that event is triggered by writing:

add_action( 'comment_post', 'send_comment_to_sugar_function', 10, 2 );

 

Basically, this line says any time a comment is saved to the Wordpress database, call the function called 'send_comment_to_sugar_function' with a priority of 10 and 2 possible parameters. For more information on Wordpress actions and hooks, check out the Wordpress Codex.

 

Now, let's write the guts for that custom function. The first thing we must do is authenticate to Sugar API. To do that, we will build the URL to the REST endpoint by concatenating the values that we stored in the plugin's settings page. This looks something like:

$api_path_auth = "/oauth2/token";
$auth_url = get_option('sugarcrm_input_url') . '/rest/v' . get_option("sugarcrm_input_api_version") . $api_path_auth;

 

On the settings page, I saved the values in the DB as "sugarcrm_input_url", "sugarcrm_input_api_version", "sugarcrm_input_username", and "sugarcrm_input_password". So, we can access those values with get_option(OPTION_NAME).

 

The oauth2/token endpoint needs parameters to return a successful authentication. Here is what we are sending:

$oauth2_token_arguments = array(
        'grant_type' => 'password',
        'client_id' => 'sugar',
        'client_secret' => '',
        'username' => get_option( 'sugarcrm_input_username' ),
        'password' => get_option( 'sugarcrm_input_password' ),
        'platform' => 'wordpress_api'
);

 

Note the "platform" parameter. This is the name you are giving to your Wordpress instance. You must go into Sugar's Admin settings to allow this platform to communicate with the Sugar REST API. The name could be anything that you like - as long as you add that same name to the Sugar API Platforms list.

Configure API Platforms page in the Sugar Admin

Now that we have our parameters to send, we need to set the cURL options. For a more detailed explanation of cURL in PHP, check out the PHP cURL manual

 

These are our options (pretty typical):

$auth_request = curl_init($auth_url);
curl_setopt($auth_request, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
curl_setopt($auth_request, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($auth_request, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($auth_request, CURLOPT_FOLLOWLOCATION, 0);
curl_setopt($auth_request, CURLOPT_HTTPHEADER, array(
    "Content-Type: application/json"
));

//convert arguments to json
$json_arguments = json_encode($oauth2_token_arguments);
curl_setopt($auth_request, CURLOPT_POSTFIELDS, $json_arguments);

 

That last line is where we are ensuring that we are POSTing to the endpoint and that we are sending a JSON encoded array of parameters.

 

Finally, we can execute the request and parse out the data that we need from the response:

$oauth2_token_response = curl_exec($auth_request);
$oauth2_token_response_obj = json_decode($oauth2_token_response);
$sugar_oauth_token = $oauth2_token_response_obj->access_token;

 

I originally had the function return the auth_token but I decided to also store the auth_token and the refresh_token values in the Wordpress cache so that I can grab them whenever I need them. This will ensures we aren't needlessly logging into Sugar over and over again.

wp_cache_set('sugar_oauth_token_access', $sugar_oauth_token);
wp_cache_set('sugar_oauth_token_refresh', $oauth2_token_response_obj->refresh_token);

 

Now that we have authenticated, we can start sending data through the Sugar REST API. Let's see if the current wordpress user is in our instance of Sugar by utilizing the global search API (/search). Since we are making a GET request, we will format our URL to include our parameters in the URL query string. I used the http_build_query function to loop through an array of parameters and construct a query string to be appended to the end of our endpoint URL. This could have been done manually.

 

Either way works as long as the final result resembles:

http://localhost:8080/sugar/rest/v11_3/search?q=mshaheen@sugarcrm.com&module_list=Contacts,Leads

With the URL setup, we need to use cURL again. Since the settings are essentially the same for each of my requests, I wrote a single function to initialize and execute our REST calls.

function curl_it($sugar_oauth_token, $curl_url, $data = null, $ispost = false) {
     $the_response = null;
     $the_request = curl_init($curl_url);
     curl_setopt($the_request, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
     curl_setopt($the_request, CURLOPT_SSL_VERIFYPEER,1);
     curl_setopt($the_request, CURLOPT_RETURNTRANSFER, 1);
     curl_setopt($the_request, CURLOPT_FOLLOWLOCATION, 0);
     curl_setopt($the_request, CURLOPT_HTTPHEADER, array(
         "Content-Type: application/json",
         "oauth-token: {$sugar_oauth_token}"
     ));
     if ($ispost) {
          curl_setopt($the_request, CURLOPT_POSTFIELDS, $data);
     }
     $the_response = curl_exec($the_request) ;
     curl_close($the_request);
     return $the_response;
}

 

The notable part of this is where we set the OAuth-Token or a standard bearer token. This wasn't necessary in the initial auth call.

curl_setopt($the_request, CURLOPT_HTTPHEADER, array(
    "Content-Type: application/json",
    "OAuth-Token: {$sugar_oauth_token}"
));

 

Now we should have a response back from Sugar. This response, if successful, will contain a record that has an e-mail address matching the one we sent in. From the returned record, we can grab the IDs that we need to be able to associate this comment with the returned user. We will add the comment as a note linked to the account record. In order to appropriately associate the new note, we'll send the Sugar User ID as the parent_id for the note and ensure that we are using the appropriate parent_type.

 

What if the e-mail address of the current Wordpress commenter can't be found in Sugar? Well then let's add them as a Lead and THEN create the new note and attach it to the newly created lead record.

 

What if the e-mail address is found but it is a Lead and not a Contact? Then we need to set the parent_type to "Leads" and the parent_id to the id of the Lead record. For anything else, we use the account_id from sugar as the parent_id.

 

Now that the plugin is doing essentially what we want it to do, there’s many enhancements we could add. The first one I threw in there was a verify button on the admin form to check if the user settings are valid. I adjusted the auth function to take the form values as parameters. That way, when we expose the function for REST calls we can pass in the values from the form BEFORE they are saved to the Wordpress database.

 

This button is essentially making the same authentication REST call that we are making when sending data to Sugar. I could have written the javascript to call the Sugar endpoint directly but that opens up a few issues. Firstly we would likely (I did) run into a Same-Origin policy conflict where the Sugar client doesn’t recognize and/or trust the external Wordpress domain. The other issue I saw was that I’d have to rewrite exactly what I did in the PHP for the front-end. I prefer reusable code. There were also some security issues to consider but these were the glaring problems from a strictly front-end implementation. SO, I simply researched how to turn my Wordpress functions within the plugin into additional endpoints in the Wordpress API. All it took was registering the route for the endpoint and telling it which function to call.

add_action( 'rest_api_init', 'init_rest_api' );

function init_rest_api () {
     register_rest_route( 'sugarcrm-api/v1', '/auth', array(
          'methods' => \WP_REST_Server::EDITABLE,
          'callback' => 'sugarcrm_rest_auth_validate',
          'args' => ['username', 'password', 'url', 'version']
     ));
}

Other tips & tricks

Much of this exercise was new to me (or I needed some refreshers). What I found was that sending the data to Sugar was not particularly difficult. The work came from deciding what to do with the information and how to handle different scenarios - like the user already being in the system as a Lead not a Contact. I spent a lot of time using the browser console to look at objects returned in the Sugar instance. I think I used this command in the console more than any other:

SUGAR.App.controller.context.get("model")

 

I would navigate to a Lead or Note record in Sugar and then run that command in the developer console to see all of the properties and values for the current module. It really helped me decide on what data to send or grab.

 

But not everything could be traced out on the front-end. So, Stack Overflow came to my rescue with a simple logging function I implemented in the PHP. With this function, I could spit out whatever I wanted to a local log file for debugging. If I wanted to see the contents of a response object, for example, I would write the line:

log_it($my_response, 'The object my_response contains: ');

 

Try it out yourself!

 

If you would like to use the code that was written for this article for some hands-on practice, you will need an instance of Sugar and an instance of Wordpress. I did it all locally with a Vagrant box for Sugar and MAMP for the Wordpress instance.

 

You don't need to install any custom code into Sugar. It uses the standard REST API and a custom API platform which can be configured easily via Sugar Admin panel.

 

The Wordpress integration code has been added to the SugarCRM Building Blocks git repository in the api-examples directory. Simply download and unpack the plugin files and drop them into your Wordpress plugins directory (typically at /wp-content/plugins). Now you should be able to activate the plugin in Wordpress and add your customized settings via the "Sugar API Plugin" settings menu option on the left navigation (like that custom logo icon? hehe).

 

Wordpress admin side navigation hi-lighting "Sugar API Plugin" option

 

Remember to register the platform in Sugar's admin section using the name "wordpress_api" or whatever you change it to in the code. That's it!

 

To try it out, you should be able to add a comment to a Wordpress post and see it attached to a record in Sugar.

 

Now go play! Here are some ideas for changes you can make to my plug-in example.

 

  • Try to add more detail to the Note created in Sugar.
  • When a comment is deleted in Wordpress, find the note in Sugar and update it.
  • Include a link to the record in Sugar that points back to the comment in Wordpress.
  • And please... share with the community what you did - no matter how minor or major.

 

I hope you enjoyed this article. I’ve never really thought of myself as a writer. In fact, my first job was working in an orange juice factory. But I got canned. I couldn’t concentrate.

Good Morning, Sugar Developers!

 

That's me! Gotta be festive at the holidays!

My name is Michael Shaheen and I am SugarCRM’s new Developer Advocate. I’ll be working with Matt Marum to maintain relationships between our developer community and the teams that work with the Sugar product group.

 

Let’s talk a bit about my background. I have been a developer since writing

10 print “Michael Rocks”
20 goto 10

to fill up the screen at the mall Radio Shack in the early 80s. Since then, I have worked for multiple companies as a manager of software development teams. I may have been managing, but I can never stop coding. I love to get into projects and solve problems. Lately that has been mostly front-end work (Javascript, React, SASS, task runners, etc), but I’m not a stranger to back-end. I spent many years coding in .NET, Java and PHP.

 

At home, I have a beautiful wife and 2 lovely teenage daughters. Oh and cats. We have 2 indoor and a few outdoor cats that we take care of. I also tell a lot of dad jokes (but that started before the kids).

 

Over the past 3 weeks, I have been learning all there is to know about SugarCRM as an organization and Sugar as a platform. I’m looking forward to talking to each of you through the Developer Community and, hopefully, in person as much as possible. From the conversations I’ve had with SugarCRM engineers, I know that they all want to put out the best quality product possible. The Developer Community is one of our top ways of gathering feedback. I aim to ensure your feedback is heard and acted upon. Keep your eyes peeled for a survey!

 

I am very excited to be in this new role and I can’t wait to meet you all through the Developer Community as well as at events as they arise. In the meantime, say hello in the comments or leave me a comment with your best dad joke. I’ll get you started:

 

Remember, 6 out of 7 dwarfs aren’t happy.