Tridion Developer Summit

February 26th, 2014 | Posted by Robert Curlette in Tridion - (0 Comments)

The first Tridion Developer Summit will be held on 15 May in Amsterdam at the Undercurrent. We have 8 sponsors, more than 15 speakers and expect around 100 developers to attend. It’s less than 80 days before the event starts and I thought I would give some info and background about the event and how the ideas came together.

The idea for the summit started last year around the time of the Tridion MVP summit in October. I thought it would be great to have a lot of Tridion developers in one place, for us to share our ideas and experiences together. To create a unique and fun experience for everyone. I wanted to have it in October, but realized there was not enough time to organize it and instead arranged a Tridion reunion for my former colleagues and I to meet and share some drinks.

After the successful reunion I had more confidence about organizing an event and I decided to explore the idea of a Tridion Programming conference. I discussed the idea with Quirijn Slings, Ron Grisnich, Nuno Linhares, Dominic Cronin, and Minko Scheltinga. We all agreed it was a great idea and we got started on the planning. After a short while I was knee-deep in venue options, dates, schedules and speakers. I needed to decide on a place and a date, and quickly, because many places were booked for months or years in advance. After much discussion with many places and dates, but without much luck, Minko and Quirijn suggested an area in Amsterdam North filled with old warehouses and artist studios. Here we found our ideal venue – big, authentic, ‘urban chic’ and right on the water – actually, the venue is floating on the water. And, the manager of the venue called me right away and seemed to really understand the unique experience I was hoping to create for the conference. To top it off, we can arrive by boat in the morning and depart by boat in the evening. The next step was the agenda and sponsors.

I contacted several Tridion developers and asked if they would like to give a talk at the Summit. They all replied with an enthusiastic ‘Yes!’. This is one of the things I love about the Tridion community – it is small, but filled with great individuals that really like to participate and help others. With the speaker list and agenda starting to take shape I contacted several Tridion partners to ask for sponsorship – and just like the speakers – everyone was very positive and most agreed to sponsor. Although it is an independent event, SDL immediately offered their support and we’ll have the Friday workshops at the SDL offices and also have the SDL film crew with us on the event day. From SDL I am happy to have the help of Nuno Linhares, Ericka Marquez and Jeff Hora. Check the sponsors page to see our great sponsors. With the venue, speakers and sponsors in place I was left to find a name for the conference and also get the website up.

The name Tridion Developer Summit is a wordplay on the old ‘TDS’ object we would often use in the templates to get objects from the system. Originally TDS stood for ‘Tridion Dialog Server’ and was the name of the product when I joined Tridion in 2000.

Ron Grisnich of Trivident took the lead for the Facebook page and before I knew it he had it set up and also tweeted the event.

For the website I decided to use an existing Event theme from WordPress instead of designing and developing the event site from scratch. After all, there is a lot of time and effort needed in all the things to organize the event itself, and I decided not to spend time programming the event website.

Hendrik Beenker from Indivirtual contacted me right away and asked if I needed any design help for the site. I said I would like help with the logo. He understood the requirements and very soon I heard that Robin Bes, a senior visual designer at Indivirtual, was available to help us. After a couple of iterations we decided on the current Tridion Developer Summit logo, and I can say I am very happy with the results. He managed to incorporate the SDL logo, old Tridion boomerang, and also it appears as a mountain, or summit. Well done!

And that takes me to where we are today. We’re fine tuning the schedule, have a great list of speakers and are still looking for few more sponsors to help us make the event a big success.

We are planning to arrive at the venue in the morning by boat from central Amsterdam to dock at the venue, but we also have 600 parking spots for those by car. We’ll have a prize giveaway at the end, based on some trivia questions. We’re also planning the pre-event evening (most likely a boat ride through Amsterdam) and Tahzoo offered to sponsor the drinks after the event. And, who knows, maybe some live music from the Tridion community as well.

Thanks a lot to everyone so far for your support. I am really excited about the event and looking forward to an amazing day. Follow us on twitter @TridionSummit and also on our facebook page. You can register on the Tridion Developer Summit website if you would like to participate in the Tridion Developer Summit 2014.

Update 17 June, 2014:

The event was a big success!  See these links below:

Read later list 2013

December 28th, 2013 | Posted by Robert Curlette in Lifehacking - (0 Comments)

Sometimes our best advice comes from within – things we know but aren’t aware of.  I have a list of ‘interesting articles’ I emailed to myself in 2013, to finish the year with a look back and also gather inspiration for 2014.  I hope you find them as interesting as I did this year.  Do you have interesting articles?  Please share in the comments.  Thanks and enjoy!

SDL Tridion

SDL Tridion ADF:  Managing Scope and Context

SDL Tridion Context Engine: What’s the context?

Future, present and history of CMSs as told by CMS expert Deane Barker

Tridion PowerShell scripts

Tridion Experience Manager Infrastructure Video

 

ServiceStack

I love the ServiceStack framework – it is a super efficient way to build Web Services, parse JSON, and interact with a database.  Most of my GUI Extensions use the ServiceStack library and I would suggest you to have a look in 2014 if you haven’t yet.

ServiceStack talk from creator Demis Bellot  (and by the way, ServiceStack v4 is now offering a paid license with official support and includes 1,192 changed files with 18,325 additions and 29,505 deletions – adding support for the latest .Net frameworks and improving the OrmLite library.  The free ServiceStack V3 is still available as open source here)

ServiceStack:  Under the covers with profiling (free video on YouTube)

ServiceStack with DotNetRocks

ServiceStack:   Recommended API Structure

SignalR and ServiceStack

 

SignalR

SignalR allows us to glue disparate systems and let them talk to each other through broadcasted events.  This allowed me to show messages in the Tridion MessageCenter GUI (JS) from distant C# code in the Tridion Event System or a Custom Resolver.  It’s now officially part of ASP.NET and is a very interesting technology.

Official site

SignalR 1.0 Rc2 (now on V2)

SignalR:  Handling Lifetime events

Using SignalR with Tridion (shameless plug)

 

Bret Victor

Genius.

Bret Victor:  Genius of our time

Bret Victor:  Inventing on Principle (Video – excellent)

Bret Victor – Global Game Jam 2013 Keynote

Bret Victor:  Stop drawing dead fish (Video)

 

Software Quality

Great perspectives from software veterans on the quality of software today.

Software Development As a Cooperative Game

Are we there yet?  Rich Hickey on OOP, massive parallelism and concurrency of the future

 Simple Made Easy:  Talk from Rich Hickey (creator of Clojure)

 Clojure Web Framework

 

.Net

Simultaneous Editing for Visual Studio with the free MultiEdit extension

.NET MVC 4:  Asynchronous Controller

.NET MVC 4:  Content topics overview

.NET:  Elmah and MiniProfiler

.NET:  Async Channel 9 Video

.NET:  MVC Solution Best Practices Video on Channel 9

Fix File Encoding issues in Visual Studio

Loading .Net User Controls with Ajax

Six Essential Language Agnostic Programming Books

.NET, HTML5 and Mobile Web:  Talk by Scott Hanselman

Azure:  Moving Images to Azure CDN

.NET MVC 5:  Getting started

ScriptCS

Crocodoc with .Net

Nested Queries in .NET EF

 

Alt.Net

Alternative solutions for the .Net platform.  I have a good feeling that F# is the future and our OO code today will look like Cobol in the future.

Stop Writing REST APIs

Redis on Windows

Rethink DB

Rethink DB .NET Driver

Thinking Functionally with F#

F# REPL

MonkeySquare Conference Videos on Vimeo

 

Screencast

15 places to find great screencasts

Screencasting Tips and Best Practices

 

Git and Package Management

Git Internals PDF

Git support in VS 2012

Chocolately Packages

 

NoSql and NodeJs

Introduction to NoSQL by Martin Fowler

Art of NodeJS

 The MEAN Stack

 

Lifehacking

This is how I work:  Zach Frechette

Hanselman’s Newsletter of Wonderful Things

Startup video talk with 37 Signals DHH and Jason 

Course:  Learning Creative Learning

 

AngularJS

Angular JS:  Design decisions

AngularJS and Grunt (Video)

AngularJS:  Where to start

AngularJS Sublime Package

Sublime Editor Web Inspector:  Debug JavaScript in Editor

Thinking in AngularJS from a jQuery background

 Chrome Dev Tools Tips

Foundation 4 HTML Template

Simple Collaborative Web Pages

 

Corona SDK

Game programming framework that uses LUA to write code and compiles to Android and iPhone.  Also, Free!

Corona SDK:  10 Tips

Corona SDK and Physics Engine

RSS and JSON with Corona SDK

 

Random

 Google Reader Alternative:  Yoleo

 Thoughts on Go after writing 3 websites

Free icons for games

100 things

For Kids:  How to train your robot

Linx Straw Toy

Seafile:  Dropbox alternative

Evernote Skitch

Learning foreign language with Language Hunters approach

Raspberry Pi Projects – Best of 2012

Raspberry Pi add-ons

 

Productivity and improvement

99u is amazing.  Every week I read something from them that inspires me to improve the quality of my work or my life.  Follow them on twitter @99u.

Holman:  Positive Feedback

How Stress Can Change the Size of Our Brains and What We Can Do to Lower it 

 99u: Every day get a small win that matters

Increasing long term happiness

Zach Holman:  Product is the by-product

37Signals DHH Podcast Interview:  How to make a dent in the universe

Why Perfection Kills Creativity

 Starting your first podcast

Inbox zero for Life

Remote Work and Quality of Life

Tips and Tricks to look better in Photos

Done is better than perfect

Offset Design Conference Videos

 99u:  Secret to feeling energized at work – Autonomy!

Office design for the future (Herman Miller)

99u Book:  Maximize your potential

10 Tricks to make yourself a gmail master 

Hanselman’s 2014 Developer tool list

4 Questions for preventing information overload

GUI Extension Installer

December 11th, 2013 | Posted by Robert Curlette in GUI Extension - (2 Comments)

The GUI Extension installer does everything on the CMS to install and register a Tridion GUI Extension, saving you time and letting you try out new GUI Extensions easier and quicker.  The idea was started at the Tridion MVP retreat and then I continued working on the installer after the retreat.  Special thanks to Dominic Cronin for sharing some PowerShell secrets to help me get started.

Run the installer at the PowerShell command line like this:

InstallExtension.ps1 ExtensionName.zip
  • Creates a Virtual Directory in IIS
  • Copies the GUI Extension files to the correct folder on the CMS Server
  • Updates the Tridion System.config with references to the GUI Extension

Try it out – a good sample extension to install is the Copy URI extension here:  https://github.com/rcurlette/CopyUri  Download the extension and you will have a CopyUri-master.zip file.  Then open PowerShell and type:

InstallExtension.ps1 CopyUri-master.zip

The file structure is important and should follow this structure:

  • /Editor/Configuration/editor.config
  • /Model/Configuration/model.config
  • /dlls

Source code

Have fun and please leave feedback!

The Tridion MVP retreat is hosted by SDL to honor those who share the most with the community throughout the year. I was lucky enough to be part of this group for my efforts in 2012.

One thing not known by many first-timers like myself is that the event is full of surprises. In this short post I will reveal some of the surprises that were in store for us during the 3 days.

Surprise 1:  Take your laptops with you to breakfast.  What?!

Turns out that after our 1 hour bus ride and for some people a 15 hour flight we would not have the luxury to relax, check in to our rooms, and get comfortable.  After a delicious breakfast buffet we headed straight to our working quarters – a large meeting room with a glass wall overlooking the beautiful valley and swimming pool.  The wireless, projector, and places were all arranged and waiting for us.  Nuno started off with an inspirational talk about how successful we were at sharing last year (hundreds of blog posts between the 20 of us) and also shared some secrets about the next version of Tridion and where the product is heading.

After his talk we had 1 hour to form teams, think of what idea we will finish programming within 2 days and launch it to the community as open source.  I joined the GUI Extension team and we decided to extend the Schema Edit screen and created the Field Behavior Injector to allow other field attributes, based on some previous code by Jaime Santos.  Bart Koopman has a nice article here about it.

Surprise 2:  Quickest way to the castle is by horse

Several of us gathered around the lobby wondering what surprise waited for us next.  We weren’t asked to bring laptops, so no surprise work awaited.  Hearing jingling bells in the distance we were all surprised when some horses and carriages appeared and this was our transportation to dinner – in the ancient castle at the top of the hill!

Surprise 3:  The local drink of Obidos is made from Cherries

Before dinner we were lucky to enjoy some drinks in the lobby and sampled some of the local digestive made from cherries – it was delicious, but a bit too sweet for a pre-dinner drink.  The bartender was very good and offered us local port and also his own Martini recipe.  Saturday we would take a trip to the local factory that produces this delicious drink – and who would have guessed that the quickest way is by off-road 4×4 vehicles driving through the Portugese countryside?

Surprise 4:  MVPs are a musical troupe

In the evenings we were surprised to learn that Quirijn Slings is a Ukelele master and has a slew of songs in his repertoire and Dominic Cronin plays a mean Harmonica.  Once the music started playing various MVPs showed off their musical talents and we had many sing-a-longs to share more with the local community.

Surprise 5:  It’s all about sharing

As soon as we started programming we also started discussing how to share what we were making.  Bart Koopman blogged about using Resource strings in GUI Extensions and John Winter wrote the first post about what our teams were building – while we were still building it.  Alvin Reyes had our Google Code site up and running with an introduction and overview immediately – preparing the location for our code the next day.  And I dug into building a GUI Extensions installer with Powershell that creates the required files, folders, IIS Settings, and config file updates.

Surprise 6:  We start everyday at 9:00 sharp in the meeting room

Despite the food and beverages consumed the previous night we were all expected to be present at the meeting room at 9:00 to begin the next days’ hacking session.  Surprisingly, it took little effort or time for us all to get sunk into hacking code and soon a strange intensity was building up in the room, and before we knew it we were making great progress on our new projects.

Surprise 7:  Your drinks are being served by the local Mayor

Our last dinner was held at an incredible local eco restaurant where they have created everything from local and sustainable materials.  It is owned by the local Mayor and he was on hand to serve drinks and invite us to taste his local specialties!  They were very delicious and he was a great host.

Surprise 8:  It’s over!

After 3 intense days we were already saying goodbyes and pushing each other to share more so we will see everyone again next year.  But, I have a feeling some of us won’t be there next year and also that some new faces might join the musical troupe to create some new songs to carry us through the night.

 

 

The Tridion GUI is powered by JavaScript while the backend is powered by .Net and sending a (nice) message from .Net to the GUI has been very challenging – until now. Using SignalR, an open-source product from Microsoft, it is possible to show or ‘echo’ the message from the .Net Event System, Templates or Customer Resolvers and show them in the GUI. Check out the tutorial that Will Price and I wrote published on SDL Tridion World to find out how to implement this yourself. Special thanks to SDL’s Bart Koopman for helping get the article online and embracing the GitHub Gist embed for source code.

Tridion 2013 works with both Visio 2010 and 2013, and removes support for older versions of Visio.  Today I tried to create a new Workflow using Visio 2013 and I would like to share an important tip that might save you some time.

The Tridion Visio Stencils are located in the ‘ADD-INS’ menu item.

However, attempting to create a new Workflow will be unsuccessful in your default Visio / Tridion workflow install.  You will see the following error message:

Visio blocks these ADD-INS for our own security (was there ever a problem with rampant Visio plugins??)

Solution:

1.  Go to File, Options
2.  Trust Center Settings


3.  File Block Settings 

4. UnCheck all checkboxes.  Yes, Uncheck them.   I know it seems unintuitive, but we are in the ‘Block’ settings and we do not want Visio to block us from opening or saving these item types.

5.  Select OK and then go back to your new Visio Workflow diagram.  It should allow you to create a new Workflow diagram now.

The icons all look almost the same, and the tooltips don’t give us any hints.  For me this was a struggle to figure out which save button to press.  Hopefully this will be fixed in a future SP.

Tridion C# TBB Upload Tip

June 10th, 2013 | Posted by Robert Curlette in .NET | Tridion - (0 Comments)

When saving a C# TBB I recently had the error message ‘Error 1 Invalid URI: The format of the URI could not be determined.’?

My first thought was to check the URI in the AssemblyInfo.cs file – was it the correct folder URI?  Did I have a typo?  Did the folder exist?  But, all was fine.

Then I recalled seeing this error before and it is related to the config.xml file that we create using TcmUploadAssembly.exe to upload the TBB.  In this file we must specify the URL of the server, which is, of course, also a URI.

The wrong value was:  <targetURL>localhost</targetURL>

Correct:  <targetURL>http://localhost</targetURL>

So, just in case you ever hit this error I hope this little tip can save you some time.

Sometimes, especially for System Administration or Developer Extensions, we don’t want to have the Context Menu GUI Extension visible to the end user.

We can use the IsAvailable option in the GUI Extension Command.js file and set it to false if the user is in a not allowed group. The following code can be used for hiding a GUI Extension context menu item.

Extensions.SetPermissions.prototype.isAvailable = function SetPermissions$isAvailable(selection, pipeline) {
    var showOption = true;
	var groupsWithNoAccess = ["Author","Chief Editor"];

	// Get the groups the user belongs to
	var groups = Tridion.UI.UserSettings.getJsonUserSettings(true).User.Data.GroupMemberships;
	if(groups["@title"] == undefined)  // 1 group membership
	{
		if(groups.Group["@title"] == groupsWithNoAccess[0])
			showOption = false;
	}
	else
	{
		// if the user belongs to a group with no access, hide the option
		for (var i = 0; i < groups.length; i++) {
		    var userIsInGroup = groups[i]["@title"];
		    length = groupsWithNoAccess.length;
		    while(length--) {
		         if (userIsInGroup.indexOf(groupsWithNoAccess[length])!=-1) {
			      showOption = false;
		              return showOption;
			 }
		     }
		}
	}
	return showOption;
};

 

Show Context Menu Item for Authorized Groups
And, if we want th do the inverse, and show the Context Menu item for only authorized groups we would use the following:


Extensions.SetPermissions.prototype.isAvailable = function SetPermissions$isAvailable(selection, pipeline) {
        var showOption = false;
	
	var groupsWithAccess = ["Administrator"];  // Specify all access groups here
	
	var groups = Tridion.UI.UserSettings.getJsonUserSettings(true).User.Data.GroupMemberships;
	if(groups["@title"] == undefined)  // 1 group membership
	{
		if(groups.Group["@title"] == groupsWithAccess[0])
			showOption = true;
	}
	else
	{
		// many group memberships
		for (var i = 0; i < groups.length; i++) {
			var userIsInGroup = groups[i]["@title"];		
			length = groupsWithAccess.length;
			while(length--) {
			   if (userIsInGroup.indexOf(groupsWithAccess[length])!=-1) {
				   showOption = true;
				   return showOption;
			   }
			}
		}
	}
	
	return showOption;
};

In my previous article I described how to use a Console application to check-in items using the Core Service. In this article I will describe how to setup and use the log4net framework to output content to the Console window and also to a log file.

1. Get the log4net DLLs using NuGet. Right-click on the project name and select ‘Manage Nuget Packages’. If you do not have this option then download NuGet and then try again.

2. Update the app.config file with the log4net configuration. NuGet packages can modify our config files, but unfortunately the log4net NuGet package does not.

This configuration does the following:
– Specifies a Console Logger to automatically write all messages to the console
– Uses a Date rolling file appender

<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level: %message%newline" />
</layout>
</appender>
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="CheckinItems.log"/>
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<maxSizeRollBackups value="7" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%-5level %date{yyyy-MM-dd HH:mm:ss} - %m%n"/>
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
<appender-ref ref="Console" />
</root>
</log4net>
</configuration>

3. Add this code to your Main method to startup log4net

XmlConfigurator.Configure();

4. Instantiate an instance of a logger

private readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

5. Write a log message

log.Info("log something");

What happens if you have hundreds, or thousands of items you want to check-in? One solution is to check-in the items using the GUI, My Tasks, All Checked out items option. However, there is another way and that is to run a script to check-in the items automatically. This is also helpful if you want to check-in all items before running another script.

Running scripts in Tridion is a common activity on large implementations. There’s always some data manipulation we want to do in bulk and save the Editors and Authors hours of time. Sometimes, however, during these bulk operations we have a failure, and sometimes items remain checked out.

In this article I will describe how to create a Core Service Console Application to check-in Tridion items. The following code uses the Tridion Core Service and can run from any Tridion instance.

Step 1: Creating the Core Service App and References

1. Create a new Console Application.

2. Reference the Tridion Core Service DLL

3. Create an App.config file

Copy the Core Service config from C:\Program Files (x86)\Tridion\bin\client\Tridion.ContentManager.CoreService.Client.dll.config

4. Reference 2 Microsoft DLLs:

  • System.Runtime.Serialization
  • System.ServiceModel

5. Add the using statement for the Tridion Core Service.

using Tridion.ContentManager.CoreService.Client;

Summary:
Creating the core Service app is relatively simple and straight-forward. The good news is we now have everything we need to get started writing some code.

Step 2: Using the Core Service to find Checked-out items

1. Set the binding. The Tridion Core Service comes with 2 binding options. It depends on your client (.Net, Java, etc) and also the ports avaialble (netTCP requires port 2660). For this example I will ue NetTCP since it is the fastest binding and most often used.

The binding is in the App.config file.

<endpoint name="netTcp_2011"

// use the endpoint name in our C# code
class Program
    {
        private static string binding = "netTcp_2011";
		//....
	}

2. Create an instance of the Core Service client and get all checked out items. * The magic here is in which filter to use. Big thanks to Andrey (aka Mr. P) for his help. how-do-i-get-a-list-of-checked-out-items-with-the-core-service

I always wrap the code in a using statement to displose of resources. The SystemWideListFilter is a special one that has magic powers to get items not conatined in any Blueprint Publication.

public static XElement FindCheckedOutItems()
{
	using (SessionAwareCoreServiceClient client = new SessionAwareCoreServiceClient(binding))
	{
		RepositoryLocalObjectsFilterData filter = new RepositoryLocalObjectsFilterData();
		XElement checkedOutItemsXml = client.GetSystemWideListXml(filter);
		return checkedOutItemsXml;
	}           
}

Step 3: Using the Core Service to Check-in items

1. Check-in the items with the Core Service

private static void CheckinItems(XElement items)
{
	using (SessionAwareCoreServiceClient client = new SessionAwareCoreServiceClient(binding))
	{
		foreach (XElement tridionItem in items.Nodes())
		{
			if (tridionItem.Attribute("Type").Value == "16" || tridionItem.Attribute("Type").Value == "64")
			{
				client.CheckIn(tridionItem.Attribute("ID").Value, new ReadOptions());
			}
		}
	}
}

That’s it. Here’s the final solution code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tridion.ContentManager.CoreService.Client;
using System.Xml.Linq;

namespace CheckinItems
{
    class Program
    {
        private static string binding = "netTcp_2011";
        static void Main(string[] args)
        {
            CheckinItems(FindCheckedOutItems());
        }

        public static XElement FindCheckedOutItems()
        {
            using (SessionAwareCoreServiceClient client = new SessionAwareCoreServiceClient(binding))
            {
                RepositoryLocalObjectsFilterData filter = new RepositoryLocalObjectsFilterData();
                XElement checkedOutItemsXml = client.GetSystemWideListXml(filter);
                return checkedOutItemsXml;
            }           
        }

        private static void CheckinItems(XElement items)
        {
            using (SessionAwareCoreServiceClient client = new SessionAwareCoreServiceClient(binding))
            {
                foreach (XElement tridionItem in items.Nodes())
                {
                    if (tridionItem.Attribute("Type").Value == "16" || tridionItem.Attribute("Type").Value == "64")
                    {
                        client.CheckIn(tridionItem.Attribute("ID").Value, new ReadOptions());
                    }
                }
            }
        }
    }
}

Summary

The Core Service is a powerful tool in our Tridion toolbelt. With the right filters we can work magic, retrieving lists of items never thought possible. A big thanks for the help from the Tridion community for sharing information!