How to prefix your routes with language code in October CMS

How to prefix your routes with language code in October CMS

Preface

Often enough I must localize the web app that I’m building. Usually it’s a quite tedious thing to code, but luckily for me October CMS already has a plugin that supports localization. The only real turn down would be that the language code is not persistent in the routes.

October is a free, open-source, self-hosted CMS platform based on the Laravel 5 PHP Framework.

I wanted my application to have this language code in the route as a prefix at all times. This is how I did it.


How to

It’s easy to implement this method, but to actually figure it out.. well, for me it was a few hours worth of headaches.

We will be building routes with this pattern:

  • http://example.com/en
  • http://example.com/en/page
  • http://example.com/ru/page
  • http://example.com/lv/page

First of all lets create a new plugin with

$ php artisan make:plugin mja.extensions

Once that’s created – go ahead and make a new file in the root directory of the plugin and call it routes.php. This code will be responsible for:

  1. Setting the locale back to the default if we are in the root route;
  2. Prefix all of the page URLs in our code ({{ ‘contact-us’|page }}).
use RainLab\Translate\Classes\Translator;

Route::matched(function($route, $request) {
    $translator = Translator::instance();
    if (!$translator->isConfigured())
        return;

    $locale = Request::segment(1);

    if ($translator->setLocale($locale) === false) {
        $translator->setLocale($translator->getDefaultLocale());
    }

    Route::getRoutes()->getByAction('Cms\Classes\Controller@run')->prefix($translator->getLocale() . '/');
});

Now that page URLs will be prefixed, we must prefix the app URLs aswell ({{ ‘/contact-us’|app }}). This can be accomplished by overwriting the existing twig extension. Add these methods to the Plugin.php file:

/**
 * Lets extend the app filter.
 * @return array
 */
public function registerMarkupTags()
{
    return [
        'filters' => [
            'app' => [$this, 'appFilter'],
        ]
    ];
}

/**
 * Extends the classic app filter
 * @param  string $url
 * @return string
 */
public function appFilter($url)
{
    return URL::to(Translator::instance()->getLocale() . '/' . $url);
}

Great! Now you can launch your app and see the magic!

But there’s more

This is just an extra, if you want to improve the language switcher. This will guarantee that the user stays on the same page after clicking the flag instead of him being redirected to the homepage.

Create a new file partials/localePicker/default.htm in your active theme.

==
function onStart() {
   $this->page['path'] = Request::path();
}
==
{% for code, name in locales %}
   <a href="{{ '/' ~ code ~ (this.page.path|slice(2)) }}">{{ name }}</a>
{% endfor %}

Fin

That’s it! Wasn’t so hard, right? Have a better solution? Be sure to post it below.

Kā var apskatīt, cik komitus katrs ir izdarījis aktīvajā Git repozitorijā (statistika)?

Kā var apskatīt, cik komitus katrs ir izdarījis aktīvajā Git repozitorijā (statistika)?

Darba gamifikācijai vai arī vienkārši, lai apskatītu skaitļus (heck, ja tu lieto Git, tad visdrīzākais, ka esi kādas pakāpes gīks – tātad tev patīk skaitļi), var izmantot šo vienkāršo Git komandu:

git shortlog -sne

Output:

    123  Matiss Janis Aboltins <matiss@mja.lv>
     12  Another User <email@example.com>
Kā es nejauši izveidoju Bites SMS spama armiju – MITM pamācība

Kā es nejauši izveidoju Bites SMS spama armiju – MITM pamācība

TL;DR: Nevajadzētu aizmirst par anti-flood sistēmu ieviešanu.

1. Priekšvārds

Droši vari tīt pāri šai sekcijai, ja tev ir puslīdz skaidrs, kā strādā internets.

Serveris & klients – kastes

Iespējams, ka Tu, lasītāj, esi kādreiz dzirdējis par to, kā īsti strādā serveri uz kurām stāv mājaslapas, bet, ja nu tomēr neesi, tad ļauj man paskaidrot.

Tavs dators un serveris ir divas kastes, kas sazinās savā starpā. Jā, tiešām! Tās raksta viena otrai vēstules. Kad tu pārlūkā ievadīji ‘http://blog.mja.lv/‘ vai noklikšķināji uz šīs saites kādā citā tīmekļa vietnē, tava personīgā kaste nosūtīja manai kastei vēstulīti: “Čau, mja.lv! Lūdzu atsūti man savu blogu!”. Mana egocentriskā kaste saņēma šo vēstuli un nolēma tai atbildēt ar milzīgu teksta blāķi, jeb citiem vārdiem sakot – šīs lapas saturu.

Tas tāds rudimentārs piemērs. Protams, tas viss ir daudz sarežģītāk, bet ar šīm zināšanām pietiks, lai saprastu sekojošo tekstu.

Telefons – nedaudz mazāka kaste

Arī tavs telefons ir kaste. Tikai nedaudz mazāka.. Lai nu kā, arī tas, ja ir pieslēgts pie interneta, tad sūta un saņem dažādas vēstules.

Datorā šīs vēstules var diezgan viegli lasīt – pietiek atvērt izstrādātāja rīkus (F12) un tur var visu labi redzēt -, bet ko darīt mobilo aplikāciju gadījumā? Telefonā jau mēs nevaram tik vienkārši atvērt izstrādātāju konsoli un apskatīt visas vēstules (varbūt var, bet tad es par to nezināju =)).

Jā, tik tālu es biju aizdomājies.. es zinu, es esmu ziņkārīgs.. ko padarīsi? Ar domu: “Kā es varētu pačekot šos mobilos pieprasījumus un kā es tos varētu eksploitēt?” es sāku rakties un meklēt risinājumus. Rezultāts – tīri interesants. Es varu bez problēmām sūtīt spama SMS milzīgā daudzumā jebkuram Bites klientam.


2. Eksploitācija – Bites drošības kļūda

MITM uzstādīšana

Kas īsti ir Man-in-the-middle-attack (jeb MITM)? Tā ir trešā persona, kas lasa visas vēstules starp serveri un klientu. Grūti to ir paskaidrot ar vārdiem.. šajā gadījumā vizuālais paraugs, manuprāt, būs pietiekami.

mitm

Lai uzstādītu vienkāršu MITM proxy uz lokālās linux mašīnas atliek tikai izdarīt šīs lietas:

  1. Izveidot datorā WiFi hotspot;
  2. sudo apt-get install mitmproxy;
  3. mitmproxy;
  4. Mobilajā ierīcē:
    1. Jāpievienojas jaunizveidotajam hotspotam;
    2. Jāuzstāda HTTP Proxy (Ip adresi var iegūt ievadot konsolē ifconfig).

2014-08-15 18.44.40

Kļūdas meklēšana..

MITM proxy serveris ir uzstādīts. Tagad varam droši vērt vaļā aplikācijas un visi pieprasījumi ies caur mūsu proxy serveri.

Šoreiz lielāku uzmanību pievērsīsim bites mobilajai aplikācijai.

Visu cieņu izstrādātājiem – Input tiek solīdi validēts un šķiet, ka nav pieļautas nekādas lielas kļūdas izņemot.. izrādās, ka es varu savu numuru pievienot vairākas reizes. Tieši cik? Nav ne jausmas, bet varu daudzas.. Par katru pievienošanas reizi man pienāk SMS. Interesanti!

2014-08-15 18.25.10

Eksploitācija

Pačekojam MITMProxy, kas līdz šim strādāja konsolē – redzam gan pieprasījumu, gan atbildi. Nu atliek tikai to noklonēt (protams, pamainot target phone number) un varam spamot.

bite-hack-pc

Citiem vārdiem sakot:

for i in {1..100}; do a="$(curl -H "Content-type: application/json" -d '[{"method":"authMSISDN","params":{"msisdn":"37128369585"}}]' http://213.226.139.54/prest/androidb.json)"; echo "$a"; done

3. Aftermath

Par šo drošības caurumu tika paziņots Bitei uzreiz pēc tā atklāšanas. Atbildes zvanu no PR cilvēka saņēmu dienas laikā un pēc pāris dienām arī no tehniķiem (lietuviešiem). Visu kļūdas labošanas laiku Bites PR cilvēks mani paturēja informētu par viņu progresu – cepuri nost.

Pāris dienas pēc pirmā zvana saņēmu arī paciņu no Bites – saldumus, tēju un portable chargeri telefonam (ķīnietis). Dienas humora deva – check.

Cik gramatikas kļūdas tu vari atrast uz šī iepakojuma? Es atradu smieklīgi daudz.

2014-08-31 16.02.26

Mēnesi vēlāk caurums bija salabots un ar mani atkal sazinājās Bites PR cilvēks, lai pateiktos un kā bounty piedāvātu bezmaksas pieslēgumu + 2GB internetu gadu Bites tīklā. Jauki no viņu puses, bet esmu vīlies. Manuprāt, šis caurums bija vērts daudz vairāk.

4. Piezīmes

  • Ja es nemaldos, tad vecais Bites SMS filtrs atļauj izsūtīt 5 SMS/sekundē. Iespējams, ka kļūdos – neesmu tik ļoti testējis vai izmantojis šo caurumu.
  • Šis drošības caurums ir salabots un vairs nestrādā. Tagad limits ir 20 SMS no 1 IP adreses.
Kā es uzlauzu Čili Picu un ieguvu piekļuvi 20k+ klientu datiem

Kā es uzlauzu Čili Picu un ieguvu piekļuvi 20k+ klientu datiem

TL;DR: Datu drošība ir ekstremāli svarīga, bet bieži vien programmētāji izvēlas par to aizmirst dēļ spiedīgiem termiņiem; Čili picas lapā bija XSS caurums.

SVARĪGI: Čili Pica tika informēta par šo drošības caurumu. Viņiem tika dots vairāk kā mēnesis laika, lai to salāpītu. Es to neesmu izmantojis personiskam labumam. No viņiem es neesmu saņēmis e-pastu ar progress report, tādēļ publicēju šo rakstu, lai izdarītu spiedienu uz radušās kritiskās situācijas risināšanu. Šis drošības caurums tagad ir salabots.

Prelude

Nav sistēmas, kuras nevarētu uzlauzt. Nav shēmas, kuras nevarētu eksploitēt. Nav datu, kuri atrodas drošībā. Viss, ko Tu interneta vidē dari tiek logots (ierakstīts) un saglabāts. Gan saites, ko tu apmeklē, gan ilgums, gan peles kustības un klikšķi, gan visas vēstules, kuras Tu izsūti. Protams, arī tādas pašsaprotamas lietas, kā tava personas informācija, kuru Tu tik vieglprātīgi atdevi kādam vietējam iepirkšanāš portālam vai ēdnīcai.

Pirms es eju detaļās par Čili Picu – vēlos, lai saproti, ka šis nav vienīgais un nav pēdējais uzņēmums, kas ir pieļāvis kļūdu savā sistēmā. Sāc dzīvot ar apziņu, ka tavi dati.. ka tava identitāte ir vērtība, kura ir jāaizsargā.

Senais Jūnijs, 2014

Jaunietis, izsalkums, garlaicība un dators – bīstama kombinācija.

Šad un tad es esmu slinks.. Nu, labi, diezgan bieži.. Lai nu kā, slinkuma dzīts es nolēmu pasūtīt picu no Čili Picas. Atvēru viņu lapu, izvēlējos savu maltīti, bet, kad mēģināju to pasūtīt – atdūros pret kaut kādu sistēmas dīvainību. Acīmredzot viņu sistēma nesaprot manu adresi.

Nu, nekas – picu var pasūtīt arī pa telefonu.

Kad pasūtījums bija veikts – atgriezos pie viņu mājaslapas, lai izpētītu, kādēļ mana adrese nestrādāja un vai es nevaru atrast vēl kādas dīvainības.

Pirmais cēliens

Kas ir pirmā lieta, ko iztestē hakeris? SQL injekcijas.

Šajā gadījumā – pret pamat-injekcijām Čili Picas lapai ir droša.

Turpinām: otrā lieta? XSS.

Džekpots. Tas bija vienkārši..

Interlude

Kas īsti ir XSS? Vienkāršā valodā runājot – neparedzēta koda injecēšana esošā lapā.

Iedomājies veikalu – teiksim to pašu Rimi. Vai šajā veikalā drīkst iebraukt ar motociklu? Visdrīzākais, ka, nē. Bet, lūk, tieši to (pārnestā nozīmē) es izdarīju Čili Picas lapā.

Eksploitācija

Šo drošības caurumu eksploitēt ir bērna spēlīte. Atliek tikai izveidot jaunu pasūtījumu ar slēptu kodu, kas atsūtītu man uz e-pastu menedžera (vai administratora) lietotāja cepumiņus. Kad tie ir iegūti – es varu tos uzlikt pats sev, jeb citiem vārdiem sakot – varu autorizēties kā šis cilvēks.

Un kam tieši tad šiem menedžeriem ir pieeja? Nekam daudz.. tikai visiem pasūtījumiem no kaut kāda 2008 gada. Tātad klientu vārdiem/uzvārdiem, e-pastiem, telefoniem, adresēm, durvju kodiem, draugu kartes nummuriem, pasūtījumu summām utt.

Tālākie soļi

Es neesmu ļaundaris. Šos datus (un veidu kā tos iegūt) es neesmu nopludinājis. Informācija par šo drošības caurumu tika nosūtīta Čili Picai, bet neesmu dzirdējis atsauksmes par progresu.

Šis drošības caurums varēja izmaksāt Čili picai diezgan skaistu naudas summiņu (ja dati tiktu nopludināti) kā arī sabiedrības (mēdiju) nosodījumu. Par šī cauruma atklāšanu es nesaņēmu pat ne vienkāršu “paldies” no Čili picas puses. Visa šī mēneša laikā bija sajūta, ka viņiem ir pilnībā vienalga par šo caurumu. Komunikācija bija briesmīga. Kauns, kauns..

Efektīvs mācīšanās veids – validate learning

Efektīvs mācīšanās veids – validate learning

Mācīties nav viegli. Bet, vai šo procesu var padarīt vienkāršāku, ātrāku un patīkamāku? Jā, varam studēt dažādus produktivitātes padomus (grāmatas, rakstus). Bet pavisam ātri nonāksim pie savas dvēseles pārdošanas produktivitātes kultam.

Kā tas ir jāsaprot? Tu kļūsti tik produktīvs, ka katru dienu 9 stundas pavadi, lasot rakstus un grāmatas par produktivitātes uzlabošanu.

Ha! Bet šis raksts nav par produktivitāti, bet gan par mācīšanos, jeb citiem vārdiem sakot, mācīšanās metodi/ideju, ko esmu pamanījis dažādās industrijās. Ja tu esi lasījis Eric Reis “The Lean Startup”, tad tu zināsi par ko es runāju. Varbūt tu esi lasījis Timother Ferriss “The 4-hour chef”? Arī šajā grāmatā parādās šis pats princips. Protams, tās nav vienīgās grāmatas, kurās šis princips tiek atspoguļots. Katrā vietā to nosauc citādāk, bet tā esence paliek nemainīga.

Ja man būtu tam jādod vārds, tad tas būtu – apstiprinoša mācīšanās. Latviski tas izklausās patizli, tādēļ ir vieglāk to atcerēties pēc angļu termina – validate learning.

Ideja ir pavisam vienkārša – ja tu vēlies kaut ko iemācīties vai uzzināt, tad izveido testu pēc kura varēsi noteikt, vai konkrētā lieta ir apgūta. Protams, pirms mēs ķeramies pie testa izveides ir nedaudz jāievāc informācija par konkrēto prasmi. Ir jāizprot tās pamat-pamat principi pirms vari tai veidot testu.

Atgriežamies pie testa izstrādes. Paskaidrošu ar piemēru. Pieņemsim, ka es vēlos iemācīties spēlēt ģitāru. Pirmā lieta, ko es daru: izveidoju pavisam vienkāršu testu:

  • Vai es protu nospēlēt G akordu?
  • Vai es protu nospēlēt C akordu?
  • ———//——— D akordu?
  • ———//——— Em akordu?
  • Vai es protu pārlikt pirkstus no G akorda uz C?
  • Vai es protu pārlikt pirkstus no G akorda uz D?
  • ———//——— G akorda uz Em?

Tātad mācību plāns man ir pilnībā skaidrs un es zinu, kas man ir jāizdara, lai nokļūtu no punkta A (nemāku spēlēt ģitāru) līdz puntkam: Awesome (māku spēlēt ģitāru).

Šķiet ļoti vienkārši, vai ne? Jā, tās nav nekādas 10`000 stundas, kā arī tie nav 20% no šīm 10k stundām (stulbs pieņēmums, ka vajag 2000 stundas kaut ko darīt, lai to iemācītos). Ir cilvēki, kas argumentē, ka pilnībā pietiek ar 20 stundām un es viņiem pilnībā piekrītu!

Lean Startup ideja ir pavisam līdzīga.

  • Izdomājam hipotēzi;
  • Izdomājam kā pārbaudīt šo hipotēzi;
  • Ieviešam produktā izmaiņas;
  • Ievācam datus hipotēzes pārbaudei;
  • Atgriežamies 1. solī.

Nekaunies uzdot stulbus jautājumus un radīt stulbas hipotēzes. Tu būsi pārsteigts, ka nereti šīs idejas ir patiesas. Piemēram: vai tu zināji, ka izveidot 100 apsveikuma kartītes ir ātrāk, ja tās veido pa vienai nevis visas kopā (visas saloka, tad visas apraksta, tad visas aizlīmē utt.)? Pieņemu, ka netici. Nekas – pamēģini!

Tātad vēlreiz: bezmērķīgi mācīties ir grūti, bet mācīties nav grūti, ja tev ir konkrēts tests/plāns/mērķis (sauc kā vēlies), lai noteiktu esošo stāvokli. Šādi tu vari ne tikai apgūt kādu prasmi, bet arī vari labāk izprast un iemācīties daudz jaunas lietas par sevi un citiem.

Vēlies uzzināt vairāk? Izlasi The Lean Startup – laba grāmata arī tiem, kas neplāno veidot biznesu. Tos pašus principus var pavisam viegli izmantot arī ikdienas dzīvē.

PyroCMS arhitektūra – noderīgs e-pasts

PyroCMS arhitektūra – noderīgs e-pasts

Varbūt Tu varētu uzrakstīt vispārīgu aprakstu par PyroCMS arhitektūru, un/vai kas šā projekta ietvaros būtu jāņem vērā?
Būtībā saprotu, ka visa programmēšana notiek pa addons/shared_addons mapi, un kāda tur ir vispārīgā hierarhija? Tur kaut kāds sava veida HMVC tiek realizēts?

PyroCMS ir būvēts ar CodeIgniter PHP framework. Konkrēti CI faili atrodas /system/codeigniter/ mapē, bet PyroCMS lietas atrodas /system/cms/ mapē. system/ folderī nevajadzētu likt neko jaunu, vai mainīt (ja nu vienīgi /system/cms/config/).

Pārsvarā strādājam veidojot dažādus Pyro moduļus /addons/shared_addons/modules.

Dizaina darbības faili atrodas /addons/shared_addons/themes/{theme_name}/. Tur ir gan view faili, gan CSS, JS un bilžu faili. Apskatot šo mapju saturu, domāju, ka struktūru būs skaidra.


Ā, un varbūt kaut ko vari pastāstīt arī par template sistēmu un view’u realizēšanu?

View faili atrodas /addons/shared_addons/themes/onfrex/views/  mapē. Tur ir vēl 3 mapes:

  • modules – moduļu view faili;
  • layouts – jeb moduļu templeiti (vecāka viewi);
  • partials – visādi sīki nieciņi, ko vajag kaut kur iekļaut.

Arī /shared_addons/modules/{module_name}/views/ ir view faili. Šie tiek izmantoti tad, ja theme mapē nav šis view fails pārrakstīts – ja tur tāds neeksistē. Tātad, ja vajag labot kādu no /modules/{module_name}/views/ failiem, tad to pārkopējam uz /themes/{theme_name}/views/modules/{module_name}/ un tur arī labojam.

6 PyroCMS speed improvement tips

6 PyroCMS speed improvement tips

PyroCMS is a content management system that’s built on Codeigniter PHP framework. For the last few years I’ve been developing on it. It has a really clean and modular codebase, but, if you use this beast improperly, it can become extremely slow. So here are a few tips on how to create fast sites with PyroCMS.

1. Enable production environment.

This simple, but yet so often forgotten task is a must do for sites that are in production mode (live for client to see).

To enable production environment you have to add this line to your .htaccess file.

SetEnv PYRO_ENV production

Or alternatively you can use this line to enable it only on a certain domain.

RewriteCond %{HTTP_HOST} ^sub.domain.com$
RewriteRule (.*) $1 [E=PYRO_ENV:production]

What does this do?

  • Enables the asset (JS, CSS) minimization;
  • Enables the asset (JS, CSS) compression;
  • Enables HTML output minimization;
  • Disabled error output (but logging will still be working);
  • Enables the usage of different config files for different environments (but this is not necessarily a speed improvement).

2. Get to know the asset config file

Located in: system/cms/config/asset.php.

Once you’ll open this file, you will a lot of comments. This file is documented brilliantly, so I don’t see much that I could explain here.

Probably the only thing that they forgot to document is the asset_filepath_callback config variable. It’s a callback that get’s called once a filename has been built. I use it to append timestamp to the asset files and I’d suggest you to do the same.

This is my code:

$config['asset_filepath_callback'] = function ($filepath, $type, $remote) {
    return $filepath . '?ts=' . filemtime($filepath);
};

3. Understand the difference between asset and theme plugins.

Do you know what’s the difference between these two lines?

{{ theme:css file="my-file.css" }}
{{ asset:css file="theme::my-file.css" group="global" }}

At a first glance they may seem quite identical, but in fact they are not so.

Lets look at the theme plugin..

<!-- The plugin.. -->
{{ theme:css file="my-file.css" }}

<!-- Results in this output -->
<link href="/addons/shared_addons/themes/theme/css/my-file.css" type="text/css" rel="stylesheet" />

Nothing strange about this, right? Everything looks as it if should, as it was expected to work.

Bet now lets look at the asset plugin (please take in consideration that we have production environment enabled).

<!-- The plugin.. but lets use many css files -->
{{ asset:css file="theme::my-file.css" group="global" }}
{{ asset:css file="theme::page.css" group="global" }}
{{ asset:css file="theme::typography.css" group="global" }}

<!-- Output the css files -->
{{ asset::render_css group="global" }}

<!-- The output -->
<link href="/assets/cache/0123gs2r2zf1.css" type="text/css" rel="stylesheet" />

Wait, why did it output only one CSS file? And why does it have such a weird filename?

That’s because by using the asset plugin in production environment the CSS (or JS) files get minimized and are combined together in a single file.

This is a really powerful feature of PyroCMS, but – with great power comes great responsibility. Use this feature wisely and don’t over-use it.

4. Enable DB caching

Another simple, but powerful speed improvement is to cache your database query results. This can be done by tweaking a bit the database.php config file.

// Production
$db[PYRO_PRODUCTION] = array(
    'hostname'      =>  'localhost',
    'username'      =>  'root',
    'password'      =>  '',
    'database'      =>  'pyrocms',
    'dbdriver'      =>  'mysqli',
    'pconnect'      =>  FALSE,
    'db_debug'      =>  FALSE,
    'cache_on'      =>  TRUE,
    'cachedir'      =>  'system/cms/cache/db-cache/',
    'char_set'      =>  'utf8',
    'dbcollat'      =>  'utf8_unicode_ci',
    'port'          =>  '3306',
);

Please notice the cache_on and cachedir lines. They are responsible for the magic.

Note: Don’t forget to create the new db-cache folder.

5. Disable the installation check hook.

This is dead simple, but often developers forget about it. After we’ve installed PyroCMS – we should disable the installation check hook. It will not give you a huge boost in speed, but every small bit counts.

The installation hook can be found in system/cms/config/hooks.php. It should look somewhat like this:

$hook['pre_controller'][] = array(
    'function' => 'check_installed',
    'filename' => 'check_installed.php',
    'filepath' => 'hooks'
);

So you just have to comment it out, or remove it completely.

6. Enable GZip compression

Read about it here.

My .htaccess file addition:

# ==== GZIP =====
# compress text, html, javascript, css, xml:
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript

# Or, compress certain file types by extension:
<files *.html>
SetOutputFilter DEFLATE
</files>
# ==== GZIP END =====

Anything else?

Did I forget about something? Perhaps you know another way how we could improve the speed of our site drastically? Feel free to leave a comment to educate me and others.

Sociālas eksperiments – bezmaksas Coffee Inn kafija

Sociālas eksperiments – bezmaksas Coffee Inn kafija

TL;DR: Kolektīva Coffee Inn bonusu kartiņa, kuru var izmantot, lai iegūtu bezmaksas kafiju.

Vai tu esi kādreiz dzirdējis par Jonathan’s Card? Tā ir Starbucks klienta karte, kurā var ieskaitīt naudu un turpmāk izmantot to, lai iegādātos kafiju. Būtībā tāda, kā papildināma dāvanu karte.

Šī karte pavisam ātri ASV kļuva populāra. To izmantoja vairāki simti cilvēku! Diez, vai Latvijā kaut kas tāds nostrādātu?

Varam mēģināt! Šajā lapā ir informācija par to, kā tu vari piedalīties šādā sociālajā eksperimentā. Atšķirībā no Jonathan’s Card, šajā kafijas kartiņā krājas bonusa punkti par katru pirkumu. Piemēram, ja tu nopērc Late par 2 EUR, tad kartei pieskaitās 0.20 EUR.

Būtībā katra 10 kafija sanāk bezmaksas, bet – neviens nezin, cik šajā kartē ir naudiņa, tātad arī neviens nevar pateikt, kurš būs šis laimīgais bezmaksas kafijas uzvarētājs.

Kādēļ nepamēģināt? Kādēļ nepievienoties? Tas jau nevienam neko nemaksā, un pie tam varbūt tieši tu būsi tas, kuram paveiksies nopirkt bezmaksas kafiju!

Pievienoties eksperimentam Atceries padalīties ar šo lapu, ja arī tu piedalies šajā eksperimentā. Jo vairāk cilvēki piedalīsies, jo labāk mums visiem!

How I hacked Airbaltic to get cheap flights

How I hacked Airbaltic to get cheap flights

TL;DR: This is a story of how I hacked Airbaltic’s cheap flight calendar to find even cheaper offers.

Screenshot from 2014-03-06 20:20:21

Airbaltic site has an interesting feature – a low fare calendar. One can choose the boarding and landing airport and see if there are any cheap flights in the selected month. All of these prices change daily. Perhaps even hourly – I’m not entirely sure.

But how can we make this even better? How can we use the calendar to find out about bargains to our desired location? This is where my hacking came in to play.

At first I had to find out all of the airport codes. This was not a hard task. To be honest – quite frankly it was as simple as taking a candy from a kid. All I had to do was open up the developers console and view the network tab. I was able to see the data that must be sent to a AJAX url and the URL itself.

Screenshot from 2014-03-06 20:19:02

Now that I had the airport codes, I had to get the prices. This task was basically the same as getting the codes, but… yes, the prices aren’t sent to the client in a JSON array. The JSON response contains HTML code. Hmm? Why would they send so much useless data? Why not simply send the prices+dates and then build the HTML on the client side?

Ah, who knows.. anyways, to get the prices I had to parse the HTML. That was not a difficult task (Ha, even for me.. a person who’s terrible at RegExp).

Screenshot from 2014-03-06 20:23:59

So now that I had aggregated all of this data, what could I do with it? Sort it by the price and see when are the cheapest flights and to where. I can store the data somewhere and pull it daily to get a sense of how the prices change, and perhaps see a pattern. Who knows.

Anyways, with this – bare price/date data – I can find quite a few bargains. For example – 30 EUR for a flight to Germany? Why not?


If you’re interested in the code go to this github repo.

Kā extendot PyroCMS base-functionality vai moduļus

Kā extendot PyroCMS base-functionality vai moduļus

Nemelošu PyroCMS nav ieplānota native moduļu extendošana. Tas ir milzīgs pain-in-the-ass, jo bieži vien esošā funkcionalitāte neapmierina.

Jebkurā gadījumā – ir iespējams extendot šos moduļus, bet tas nav pavisam vienkārši. Šajā rakstā centīšos paskaidrot, kā to pēc iespējas nesāpīgāk izdarīt. Iespējams, ka vairākas lietas var izdarīt labāk/ātrāk/vienkāršāk. Ja tev ir kādi ieteikumi – labprāt tos uzklausīšu.


Pirms uzsākam kaut ko darīt – pamainīsim failu ielādes kārtību. Par cik mēs vēlamies, lai mūsu overwrite klases tiktu ielādētas pirms īstajām PyroCMS klasēm, ir jāpamaina ielādes kārtība.

To var izdarīt pavisam vienkārši (bet nekur tas nav nodokumentēts…). Faila cms/config/config.php pašā apakšā tiek uzsetots mainīgais modules_locations. Šis kalpo arī kā failu ielādes kārtība. Tātad, mums vajadzīgā kārtība būtu šāda:

#!/bin/php
$config['modules_locations'] = array(
    ADDON_FOLDER.'__SITE_REF__/modules/' => '../../../addons/__SITE_REF__/modules/',
    SHARED_ADDONPATH.'modules/' => '../../../addons/shared_addons/modules/',
    APPPATH.'modules/' => '../modules/',
);

Jeb citiem vārdiem sakot – meklējam addons/default/ mapē failus, tad addons/shared_addons/ un tikai tad system/cms/.

modules/{name}/config/

Neesmu iedziļinājies (nav bijusi vajadzība), kā šo sadaļu var extendot. Ja tev ir idejas – droši raksti.

modules/{name}/controllers/

Šī ir viena no svarīgākajām daļām, ko extendosim. Ideja ir pavisam vienkārša:

  1. Ielādējam jauno MY_{controller_name}.php failu
  2. Extendojam veco {controller_name}.php failu
  3. Live hapily ever after

Kā to izdarīt?

Izveidojam jaunu routes ceļu uz šo kontroleri

Par cik mēs vēlamies extendot default kontroleri nevis to pilnībā pārrakstīt (overwrite), tad nevaram arī izmantot defaultā kontrolera vārdu (jo @PHP nevar būt divas klases ar to pašu vārdu).

Tātad, lai izmantotu kontroleri ar citu vārdu, mums ir jāizveido route, kas norādīs uz šo, jauno kontroleri.

system/cms/config/routes.php

#!/bin/php
// We still want to use this
$route['admin/navigation/groups/(:any)']  = 'navigation/admin_groups/$1';

// Overwrite the default core modules with a custom one (if one exists)
$route['admin/navigation/(:any)']         = 'navigation/my_admin/$1';
$route['admin/navigation']                = 'navigation/my_admin/index';

Šis ir piemērs navigation moduļa admin kontrolerim. Par cik eksistē arī admin_groups kontroleris un to mēs ne-extendojam, tad ir arī jānorāda, ka šajā gadījumā izmantosim veco kontroleri.

Izveidojam jauno kontroleri

addons/shared_addons/modules/{name}/MY_{controller_name}.php

#!/bin/php

// Get the parent
require(set_realpath(APPPATH . 'modules/navigation/controllers/admin.php'));

/**
 * Extends the PyroCMS navigation admin controller
 */
Class MY_Admin extends Admin {

    private $validation_rules = array(
        /**
         * There are actually more rules here. Most
         * of them are located in navigation/admin.php
         * constroller.
         *
         * You can set custom ones right here if your
         * heart desires to do so.
         */
    );

    /**
     * Constructor
     */
    function __construct()
    {
        parent::__construct();

        // Set more rules
        $this->form_validation->set_rules($this->validation_rules);

        // And of curse.. an overwrite for the model
        $this->navigation_m = $this->load->model('MY_navigation_m');
    }

    public function edit($id = 0)
    {
        $this->template->set('variable', 'Hello World');

        // Call the parent with the proper params
        call_user_func_array(array($this, 'parent::edit'), func_get_args());
    }
}

Domāju, ka nav jāpaskaidro šis, sample kods. Būtībā visas šīs sample metodes var aizvākt un aizstāt ar citām, vajadzīgajām, bet, navigation_m mainīgā setošanu gan nevajadzētu aizvākt (paskaidrošu sekcijā par moduļu ekstendošanu).

Kā redzam, My_Admin klase extendo Admin klasi, kas tiek ielādēta ar require() funkcijas palīdzību nedaudz augstāk.

Funkcija call_user_func_array() tiek izmantota, jo var sanākt, ka nākotnē PyroCMS edit() metodei pieliek vēl kādu argumentu. Šī funkcija nodrošina to, ka edit() metode strādās neatkarīgi no argumentu daudzuma.

Protams, var darīt arī šādi, bet tad jāuzmanās no nākotnes apgreidiem, lai tie neko nesaplēš.

#!/bin/php
public function edit($id = 0)
{
    parent::edit($id);
}

modules/{name}/language/

Neesmu iedziļinājies (nav bijusi vajadzība), kā šo sadaļu var extendot. Ja tev ir idejas – droši raksti.

modules/{name}/libraries/

Neesmu iedziļinājies (nav bijusi vajadzība), kā šo sadaļu var extendot. Ja tev ir idejas – droši raksti.

Iespējams, ka MY_{library_name}.php triks strādās arī te (līdzīgi kā ar kontroleriem). T.i. – Class MY_{library_name} extends {library_name} {.

modules/{name}/models/

Modeļus extendot var līdzīgi kontroleriem (bet, protams, bez routinga). Tātad..

addons/shared_addons/modules/{name}/models/MY_{model_name}.php

#!/bin/php

if (class_exists('navigation_m') === FALSE)
    get_instance()->load->model('navigation/navigation_m');

/**
 * Extend/overwrite the default functionality of
 * navigation_m model
 */
Class MY_Navigation_m extends Navigation_m
{
    function __construct()
    {
        parent::__construct();
        get_instance()->navigation_m = $this;
        
        public function delete_link($id = 0)
        {
            parent::delete_link($id);

            // My custom functionality goes here

            return TRUE;
        }
}

modules/{name}/views/

View faili nenodrošina extendošanu. Es neredzu situāciju, kad tā būtu vajadzīga.

View faili nodrošina overraidošanu. Tātad, ja vēlamies izveidot jaunus formas laukus vai citādāk pamainīt defaulto HTML (piemēram, admin panelī), tad droši izveidojam identisku failu jau moduļu struktūrai un strādājam tajā.

T.i. addons/shared_addons/modules/navigation/views/index.php = system/cms/modules/navigation/views/index.php. Strādājam shared_addons index failā.

modules/{name}/plugin.php

Pluginus extendot ir, manuprāt, vissarežģītāk. Daudzas metodes šajos failos ir private vai protected, tātad tās nevēlas, lai kāds jauktos kaut kur pa vidu.

Vienīgais veids, kā man sanāca extendot šo lapas sadaļu bija duplicējot kodu. Citiem vārdiem sakot – nokopēju visu veco funkcionalitāti un iekopēju jaunā failā.

  • Faila atrašanās vieta: addons/shared_addons/modules/{name}/plugin.php
  • Klases header: Class MY_{name}_Plugin extends Plugins {

Diemžēl šī funkcionalitāte nestrādās out-of-the-box. Ir jāveic modifikācijas Plugins.php bāzes bibliotēkai, lai:

  1. Tiktu izmantots MY_{name}_Plugin (preferably) nevis {name}_Plugin
  2. Tiktu ielādēts šis jaunais plugin fails (by default ielāde nenotiek pareizajā secībā – šī secība ir hardkodēta)

Tātad jaunais fails:

addons/shared_addons/libraries/Plugin.php

#!/bin/php

Class MY_Plugins extends Plugins {

    private $loaded = array();

    public function __construct()
    {
        parent::__construct();
        $this->_ci = & get_instance();
    }

    public function locate($plugin, $attributes, $content)
    {
        if (strpos($plugin, ':') === FALSE)
        {
            return FALSE;
        }
        // Setup our paths from the data array
        list($class, $method) = explode(':', $plugin);
        $locations = $this->get_locations();

        foreach ($locations as $directory)
        {
            if (file_exists($path = $directory.'plugins/'.$class.'.php'))
            {
                return $this->_process($path, $class, $method, $attributes, $content);
            }

            else {
                if (defined('ADMIN_THEME') and file_exists($path = APPPATH.'themes/'.ADMIN_THEME.'/plugins/'.$class.'.php'))
                {
                    return $this->_process($path, $class, $method, $attributes, $content);
                }
            }

            // Maybe it's a module
            if (module_exists($class))
            {
                if (file_exists($path = $directory.'modules/'.$class.'/plugin.php'))
                {
                    $dirname = dirname($path).'/';

                    // Set the module as a package so I can load stuff
                    $this->_ci->load->add_package_path($dirname);

                    $response = $this->_process($path, $class, $method, $attributes, $content);

                    $this->_ci->load->remove_package_path($dirname);

                    return $response;
                }
            }
        }

        log_message('debug', 'Unable to load: '.$class);

        return false;
    }

    private function _process($path, $class, $method, $attributes, $content)
    {
        $class = strtolower($class);
        $class_name = 'Plugin_'.ucfirst($class);

        if ( ! isset($this->loaded[$class]))
        {
            include $path;
            $this->loaded[$class] = true;
        }

        if ( ! class_exists($class_name) && ! class_exists('MY_' . $class_name))
        {
            //throw new Exception('Plugin "' . $class_name . '" does not exist.');
            //return FALSE;

            log_message('error', 'Plugin class "'.$class_name.'" does not exist.');

            return false;
        }

        if (class_exists('MY_' . $class_name))
        {
            $class_name = 'MY_' . $class_name;
        }

        $class_init = new $class_name;
        $class_init->set_data($content, $attributes);

        if ( ! is_callable(array($class_init, $method)))
        {
            // But does a property exist by that name?
            if (property_exists($class_init, $method))
            {
                return true;
            }

            //throw new Exception('Method "' . $method . '" does not exist in plugin "' . $class_name . '".');
            //return FALSE;

            log_message('error', 'Plugin method "'.$method.'" does not exist on class "'.$class_name.'".');

            return false;
        }

        return call_user_func(array($class_init, $method));
    }

    /**
     * Set the proper loading order
     * @return array
     */
    public function get_locations()
    {
        $return = array();
        $locations = $this->_ci->config->item('modules_locations');

        foreach ($locations AS $loc => $val)
        {
            if (strpos($loc, SHARED_ADDONPATH) !== FALSE)
            {
                $return[] = SHARED_ADDONPATH;
            }
            else if (strpos($loc, ADDON_FOLDER) !== FALSE)
            {
                $return[] = ADDONPATH;
            }
            else if (strpos($loc, APPPATH) !== FALSE)
            {
                $return[] = APPPATH;
            }
        }

        return $return;
    }
}

Kā redzam – tiek dublēta jau esošā PyroCMS Plugins.php funkcionalitāte. Diemžēl to apiet nevaram. Lai nu kā, šī faila jauninājumi: get_locations() && MY_ klašu meklēšana.

cms/core/

Iespējams, ka šo var extendot, izveidojot addons/shared_addons/core/ mapi un tur ieliekot MY_{classname}.php failu (varbūt pat MY_MY_{classname}, lai veiktu trešā līmeņa extendošanu?). Neesmu šo iztestējis. ja zini kā to izdarīt – droši raksti.

cms/libraries/

Skatīt sadaļu nedaudz augstāk par plugins.php extendošanu. Ideja pavisam vienkārša: šajā mapē varam likt MY_{classname}.php klases. Tās tad arī extendos esošās, default klases.

cms/helpers/

Iespējams, ka MY_{helper_name}.php strādās. Jāiztestē.