середу, 30 березня 2016 р.

Як я хакнув illuminated cloud плаґін до Intellij Idea

Один з моїх джуніків показав мені прикольний плаґін, який дозволяє робити контрол-клік, це та фіча, якої мені вічно бракувало в Force.com IDE ( екліпсі для сейлзфорса ), так само її немає в Mavensmate плаґіні до саблайма.
Єдина проблема була в тому, що той плаґін був платний, і що давав 30 днів пробного періоду, а за такий короткий час той плаґін навіть не розпробуєш.
Тому я зацікавився найперше, як цей плаґін визначає, що закінчився термін пробного періоду.
Пробував шукати в реєстрі ключі з ім'ям або текстом Illuminated, не знайшов нічого. Потім методом проб і помилок знайшов папку .IdeaIC2016\config\plugins\IlluminatedCloud\lib\TurboActivate і знайшов там TurboActivate.dat і TurboActivate.dll.
Також після того я знайшов одну статтю, де один чувак пише про те, як обійти захист TurboActivate http://www.manhunter.ru/underground/683_issledovanie_zaschiti_programmi_letasoft_sound_booster.html і, хоча я зовсім не знаю асемблера, вирішив спробувати скачати IDA і пошукати там значення, яке відповідає за визначення, чи ліцензія активна, чи ні. Однак в мене виникли проблеми з тим, що я скачав 32-бітну версію IDA і нею я міг патчити тільки 32-бітну бібліотеку TurboActivate.dll, а мій плаґін використовував, вочевидь, 64-бітну версію цієї бібліотеки, тому навіть після того, як я хакнув TurboActivate.dll, мені далі вискакувало повідомлення, що залишилося 8 днів пробного періоду. Тоді я спробував взагалі видалити 64-бітну версію цієї бібліотеки разом з папкою.
Помилка, що виникла при наступному запуску ідешки
Caused by: java.lang.Error: The system cannot find the path specified.
    at com.sun.jna.Native.open(Native Method)
    at com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:171)
    at com.sun.jna.NativeLibrary.getInstance(NativeLibrary.java:398)
    at com.sun.jna.Library$Handler.<init>(Library.java:147)
    at com.sun.jna.Native.loadLibrary(Native.java:412)
    at com.sun.jna.Native.loadLibrary(Native.java:391)
    at turboactivate.TurboActivateNative.<clinit>(SourceFile:1113)
    at turboactivate.TurboActivate.SetCustomProxy(SourceFile:763)
    at com.illuminatedcloud.intellij.license.LicenseValidator.configureProxy(SourceFile:141)
    at com.illuminatedcloud.intellij.license.LicenseValidator.validateLicense(SourceFile:76)
    at com.illuminatedcloud.intellij.parser.ApexParserDefinition.<init>(SourceFile:38)
наштовхнула мене на іншу думку. А навіщо мені взагалі лізти в ту бібліотеку і асемблер, якщо можна просто відключити на рівні класу LicenseValidator? Я розпакував джарку, знайшов байткод класу LicenseValidator, дизасемблював його за допомогою цього сайту: http://www.javadecompilers.com/, замінив його код на такий, що повертає завжди тру на запит про те, чи пакет ліцензований, і який навіть не шукає і не запускає бібліотеки TurboActivate. Зробив проект в екліпсі, скомпілював його, перемістив скомпільовану версію цього класу в джарку, запустив інтеліджей айдію - вуаля, все працює, набридливе повідомлення про те, що залишилося 8 днів пробного періоду, зникло. Ура!

суботу, 26 березня 2016 р.

Нарешті Я Salesforce.com Force.com Certified Advanced Developer


Нарешті я отримав звання Salesforce.com Force.com Certified Advanced Developer, поки я був у лікарні.
Ура! Я так довго чекав цього !!!!!!

Шановний Богдан Довгань,

Вітаємо! Ви успішно завершили сертифікаційний іспит, щоб отримати звання Salesforce.com Force.com Certified Advanced Developer. Ласкаво просимо в світову спільноту сертифікованих фахівців Salesforce.com !

Ім'я здавача іспиту: Богдана Довгань
Іспит: Salesforce Завдання на програмування для сертифікації - Adv Dev (SU15) (30 жовтня)
Результат: ЗДАВ
Дата завершення: 03/17/2016






вівторок, 8 березня 2016 р.

понеділок, 26 жовтня 2015 р.

Salesforce: фіксаємо помилку "Current Company is not set"

Чудова стаття про те, як пофіксати помилку Current Company is not set.


Загалом під час тесту можуть траплятися дві помилки:

1. Current Company is not set.

2. This operation cannot be performed in multi-company mode

Загалом ці помилки залежать від кількості джанкшин-обджект рекордів між юзером і кода-компані. Якщо ці квері

[ select Id from c2g__codaUserCompany__c where c2g__User__c = :UserInfo.getUserId() ].size()
[ select Count(Id) from c2g__codaUserCompany__c where c2g__User__c = :UserInfo.getUserId() ]
повертають число 0, тоді маємо помилку "Current Company is not set", якщо вони повертають число, більше за одиницю, тоді маємо помилку "This operation cannot be performed in multi-company mode".

Щоб не було помилки, потрібно мати рівно один запис в базі даних для джанкшин-обджекта кода-юзер-компані c2g__codaUserCompany__c. Тому бажано мати @isTest(seeAllData=false), бо якщо поставити @isTest(seeAllData=true). можуть бути видимі записи, які існують на сендбоксі чи продакшині.



Ось код з оригінальної статті.



@isTest 
private class salesInvoiceTestClass {
 static testMethod void salesInvoiceTest() {
 Group testGroup = new Group(Name='test group', Type='Queue');
 insert testGroup;
 QueuesObject testQueue ; 
 System.runAs(new User(Id=UserInfo.getUserId())) {
     List<queuesobject >  listQueue = new List<queuesobject >();
   queuesobject q1 = new queuesobject (queueid=testGroup.id, sobjecttype='Case'); 
   listQueue.add(q1);
   queuesobject q2 = new queuesobject (queueid=testGroup.id,                                                                 sobjecttype='c2g__codaAccountingCurrency__c'); 
   listQueue.add(q2);
   queuesobject q3 = new queuesobject (queueid=testGroup.id,                                                                 sobjecttype='c2g__codaPurchaseInvoice__c'); 
   listQueue.add(q3);
   queuesobject q4 = new queuesobject (queueid=testGroup.id, sobjecttype='c2g__codaCompany__c'); 
   listQueue.add(q4);
   queuesobject q5 = new queuesobject (queueid=testGroup.id, sobjecttype='c2g__codaYear__c'); 
   listQueue.add(q5);
   queuesobject q6 = new queuesobject (queueid=testGroup.id, sobjecttype='c2g__codaInvoice__c'); 
   listQueue.add(q6);
   insert  listQueue;

   GroupMember GroupMemberObj = new GroupMember();
   GroupMemberObj.GroupId = testGroup.id;
   GroupMemberObj.UserOrGroupId = UserInfo.getUserId();
   insert GroupMemberObj;
 }        

 c2g__codaCompany__c company = new c2g__codaCompany__c();
 company.Name = 'Test Record';
 company.c2g__CashMatchingCurrencyMode__c = 'Test Account';
 company.c2g__YearEndMode__c = 'Test Code';
 company.c2g__ExternalId__c = 'ABCDE1234567876';
 company.c2g__LogoURL__c ='ww.XYZ.com';
 company.c2g__ECCountryCode__c = 'AE' ;
 company.c2g__VATRegistrationNumber__c = 'Test 222.222.222 TVA' ;
 company.c2g__Website__c = 'ww.xyz.com';
 company.c2g__Country__c ='US';
 company.ownerid = testGroup.Id;
 insert company;

 c2g__codaYear__c yr= new c2g__codaYear__c();
 yr.Name ='2015';
 yr.c2g__AutomaticPeriodList__c =  true;
 yr.c2g__OwnerCompany__c = company.id;
 yr.c2g__ExternalId__c = 'yzsd1234';
 yr.c2g__NumberOfPeriods__c =11;
 yr.c2g__StartDate__c =  system.today() - 10;
 yr.c2g__Status__c = 'Open';
 yr.c2g__PeriodCalculationBasis__c = '445';
 yr.c2g__YearEndMode__c = 'Full Accounting Code' ; 
 yr.c2g__UnitOfWork__c = 12;
 yr.ownerid = testGroup.Id;
 insert yr;

 c2g__codaPeriod__c prd = new c2g__codaPeriod__c();
 prd.Name ='Test2015';
 prd.c2g__ExternalId__c ='abdc12345';
 prd.c2g__StartDate__c = System.today()-10;
 prd.c2g__EndDate__c= System.today()+10;
 prd.c2g__OwnerCompany__c = company.id;
 prd.c2g__PeriodNumber__c ='123';
 prd.c2g__Description__c ='test Desc';
 prd.c2g__PeriodGroup__c = 'Q1';
 prd.c2g__PeriodNumber__c = '1';
 prd.c2g__YearName__c = yr.id;
 insert prd;

 c2g__codaUserCompany__c userCompany = new c2g__codaUserCompany__c();
 userCompany.c2g__Company__c =company.id;
 userCompany.c2g__User__c = userInfo.getUserId();
 userCompany.c2g__ExternalId__c = 'ABCDE1234567876';
 userCompany.c2g__UnitOfWork__c = 111 ;
 insert  userCompany;

 c2g__codaAccountingCurrency__c accCurrency = new c2g__codaAccountingCurrency__c();
 accCurrency.c2g__OwnerCompany__c = company.id;
 accCurrency.c2g__DecimalPlaces__c = 2;
 accCurrency.Name = 'AED';
 accCurrency.c2g__Dual__c = true ;
 accCurrency.ownerid = testGroup.Id;
 insert accCurrency;

 c2g__codaExchangeRate__c exchRate = new c2g__codaExchangeRate__c();
 exchRate.c2g__ExchangeRateCurrency__c = accCurrency.id;
 exchRate.c2g__OwnerCompany__c = company.id;
 exchRate.c2g__ExternalId__c ='12323232';
 exchRate.c2g__Rate__c =44.55;
 exchRate.c2g__StartDate__c = system.today()-10;
 exchRate.c2g__UnitOfWork__c =10;
 insert exchRate;       

 c2g__codaGeneralLedgerAccount__c GLAcc = new c2g__codaGeneralLedgerAccount__c();
 GLAcc.Name = 'Retained Earnings';
 GLAcc.c2g__BalanceSheet1__c ='Balance Sheet'; 
 GLAcc.c2g__ExternalId__c ='testID';
 GLAcc.c2g__ReportingCode__c = '1234567543333';
 GLAcc.c2g__UnitOfWork__c =123;
 GLAcc.c2g__TrialBalance1__c = 'Balance Sheet' ;
 GLAcc.c2g__Type__c = 'Balance Sheet' ;
 insert GLAcc;

 Account acc= new Account();
 acc.Name='Test Account';
 acc.CurrencyIsoCode='USD';
 acc.c2g__CODAAccountsPayableControl__c = GLAcc.Id;
 insert acc;

 c2g__codaInvoice__c testInvoice = new c2g__codaInvoice__c();
 testInvoice.CurrencyIsoCode = 'USD'
 testInvoice.c2g__InvoiceDate__c = date.today().addDays(-7)
 testInvoice.c2g__DueDate__c = date.today().addDays(-7)
 testInvoice.c2g__Account__c = acc.Id
 testInvoice.c2g__OwnerCompany__c = company.id
 testInvoice.ownerid = testGroup.Id
 insert testInvoice;            
 }  
}

середу, 21 жовтня 2015 р.

Twilio Сервіс для відправлення смсок


Минулого місяця колеги розповіли про такий цікавий сервіс Твіліо, за допомогою якого можна відсилати смски на верифіковані номери.
Додати номер до списку верифікованих можна на своєму профайлі, після чого клацнути веріфай - щоб на вказаний номер подзвонили чи відправили смс з кодом, який треба ввести для підтвердження верифікації.
При реєстрації треба верифікувати хоча б один номер.
Глобальні налаштування містять країни, куди дозволяється відсилати смски, якщо не вибрати там України, буде видавати помилку "Permission to send an SMS has not been enabled for the region indicated by the 'To' number:" 
Можливо комусь буде цікаво.
Там я не давав посилання на реалізацію свою, ось сторінка, з якої можна мені відправляти смски
У своїй версії я не сильно змінив реалізацію, єдине що, то це змінив едітбокс на дропдаун, оскільки у пробній безкоштовній версії можна відправляти смски лише на верифіковані номери, а не на всі.
При налаштуванні цього сервісу буде потрібно пройти усі кроки, що подані на сторінці з кодом, тобто:
  1. Зареєструвати безкоштовну пробну версію сервісу, створити номер, з якого будуть відправлятися смс, верифікувати усі номери, на які збираєтесь відправляти смски.
  2. Додати віддалений сайт (<ваш інстанс>/0rp/e)  https://api.twilio.com
  3. Створити клас Sendsms
    public class Sendsms {
        public String phNumber{get;set;}
        public String smsBody{get;set;}
        String accountSid;
        string token;
        String fromPhNumber;
        errorResponseWrapper erw;
        public sendsms(){
            phNumber ='+'+Apexpages.currentpage().getparameters().get('phNumber');
            accountSid = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
            token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
            fromPhNumber = 'xxxxxxxx';
        }
        public void processSms(){
            HttpRequest req = new HttpRequest();
            req.setEndpoint('https://api.twilio.com/2010-04-01/Accounts/'+accountSid+'/SMS/Messages.json');
            req.setMethod('POST');
            String VERSION  = '3.2.0';
            req.setHeader('X-Twilio-Client', 'salesforce-' + VERSION);
            req.setHeader('User-Agent', 'twilio-salesforce/' + VERSION);
            req.setHeader('Accept', 'application/json');
            req.setHeader('Accept-Charset', 'utf-8');
            req.setHeader('Authorization','Basic '+EncodingUtil.base64Encode(Blob.valueOf(accountSid+':' +token)));
            req.setBody('To='+EncodingUtil.urlEncode(phNumber,'UTF-8')+'&From='+EncodingUtil.urlEncode(fromPhNumber,'UTF-8')+'&Body='+smsBody);
            Http http = new Http();
            HTTPResponse res = http.send(req);
            if(res.getStatusCode()==201)
                ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'SMS Sent Successfully'));
            else{
                erw =(errorResponseWrapper)json.deserialize(res.getBody(),errorResponseWrapper.class);
                ApexPages.addmessage(new ApexPages.message(ApexPages.severity.ERROR,erw.message));
            }
        }
        public class errorResponseWrapper{
            String code;
            String message;
            String moreInfo;
            String status;    
        }
    }
  4. Створити сторінку
    <apex:page controller="Sendsms" sidebar="false" showHeader="false" title="Send SMS">
    <apex:pagemessages />
    <apex:form >
    <br/>
    <center><b>Send SMS</b> <br/><br/>
    <b>To</b> &nbsp;&nbsp;&nbsp;&nbsp;<apex:inputtext value="{!phNumber}"/> <br/><br/><br/>
    <b>Body</b>&nbsp;<apex:inputtext value="{!smsBody}"  /> &nbsp;<br/>(160 Char Max)<br/><br/><br/>
    <apex:commandButton value="Send Sms" action="{!processSms}"/>
    </center>
    </apex:form>
    </apex:page>
  5. Перейти на дану сторінку або створити додаткову кнопку для відправлення смс.
  6. Профіт!!!
От тільки з останнім пунктом проблема. Не зрозуміло, як це можна монетизувати, і як можна отримати з цього прибуток?
Можна було б заплатити за платну версію, щоб зробити доступним відправлення смс на всі номери, але що з того? Тільки витрати, і жодного прибутку. Може, хтось має якусь ідею для монетизації?

понеділок, 19 жовтня 2015 р.

Нові фічі релізу Зима'16: Кеші і розбиття


 В новому релізі хмарної платформи (самі знаєте, якої) з'явилося нове поняття. Кеші. Як підказує нам Васюник, є два кеші: Cache.Session (кеш сесії) та Cache.Org (кеш організації).
Дані в першому кеші прив'язані до сесії певного юзера, дані в другому кеші прив'язані лише до організації і не прив'язані до сесії чи користувача.


Також є два розбиття. Кеш можна розбити на декілька розбиттів, кожне з яких матиме свої дані.
Кеш сесії можна безпосередньо використовувати у Віжуелфорс сторінках через синтаксис {!Cache.Session.простір_імен.назва_розбиття.ключ}.
Однак до цього кешу не можна доступитися з анонімного блоку виконання, натомість до кешу організації можна.
Кожен зі згаданих класів поводиться як мапа, кожен з них має методи "put", "get", "contains".

пʼятницю, 24 квітня 2015 р.

Потрібні автори\перекладачі

Останнім часом я рідко коли наповнював цей блоґ, це пов'язано з тим, що він має малу відвідуваність - мало хто цікавиться матеріалами про технологію Salesforce українською мовою, тому я перестав писати україномовні статті, а здебільшого писав англомовні статті у іншому блозі на вордпресі, деякі з яких недавно почали перепощувати на блозі компанії, де я зараз працюю, наприклад, цю статтю, цю, цю і цю.
Не знаю, чи когось все таки цікавлять матеріали про Salesforce українською мовою, якщо так - то запрошую до співпраці. Можете звертатись до мене особисто або просто прокоментувати цю публікацію, якщо Ви бажаєте писати якісь україномовні статті в цій юзергрупі або перекладати існуючі статті написані мною чи ті, які я перепощую на особистому блозі - серед них є справді цікаві матеріали, однак в мене немає часу перекладати їх українською, і дуже сумнівно, чи це комусь потрібно.