So, how do you get fully accepted into Adsense these days? (August 2016)

Thursday, August 4, 2016

Lots of people keep making videos about how you have to do this and target that so that maybe one day the Adsense people will take you in and that if you never get fully enrolled then guess what there are other ad programs out there so that Google ain't the best thing on earth afterall and so on and so on... STOP



I thought I'd write a little article that will save you some time and frustration, hopefully lightening your path a little. That supposes of course that you have applied at least twice to get full acceptance into the program and that currently you're in a state of almost wanting to bang your head against the wall because you can't figure out what it takes to get Adsense and that it makes you feel psychotic every time you start thinking about it.

Fine, but RolandC.net, what do I need to do to get fully accepted into the Adsense program?

The answer is both simple and short: BE TRULY USEFUL to someone (not just to yourself).

Let me clarify what I just wrote. I did not even have 18 (probably 17 or 16) articles when I applied (and got full acceptance), so don't even listen to those who recommend having at least 20-25 quality articles.

And frankly, quality is not even the point here. Google does not need quality, it needs usefulness, hear problem solving, creating solutions.

Before I elaborate on this, a little word about traffic, as not only I remember having had less than 18 posts for sure, I was also averaging the 30 visits a day or so, probably even less at that time. Felt lonely on my Blogger platform...well who cares, I did get accepted anyway 

Of course, your content should reflect the fact that you've benefited from some sort of, quote on quote, education somewhere along the way, and your website/blog's visual identity should convey your own personal spirit or your product/services's spirit or whatever, but that's not to the point, the point being that your content should solve problems, bring solutions (and not just to you but to people that are actually real and that actually need your content, so that the reader went 'Oh, gee! thanks so much for your article, it really saved my day!' or 'Gosh, I didn't know that! I'm so happy I found your blog!' or even 'Wow, this is so interesting, I gotta try that on my own too!'.


Bang! there you have it, if you can help people, if you can really be useful to anyone on this planet, then you will attract people (potential customers) and then advertisers want to know about you - here's your Adsense ID coming up right now! (well actually they first give you a link to download the Adsense app :))

Anyway, I'm not going to mention anything about the 'you have to be passionate' thing because it's obvious or it should obvious that if you write articles about something you know enough to help the community, that means you love what you do enough to be doing it, right? So if you have started creating content just to get Adsense, hoping you'll only have to sit on it afterwards waiting for Paypal® to come thru while sipping your beverage of choice on some sand beach somewhere, then this article wasn't for you to read, sorry.

Think about it this way, as an advertiser, would you invest money in people who don't have nothing helpful to tell (hear sell)?  Would you invest your hard-earned money in people who only keep sharing things they haven't even created themselves? Would you invest in people who're only looking to get an income from advertising while not even trying to understand what advertisers need from them? (advertisers - those are the people who will pay you money in the end) and would you invest in people that....I forgot, sorry.

Anyway I hope you've enjoyed reading this article, that it'll help you either getting Adsense quick and easy at last (though truthfully), or find something else (better) to do with your life (you might wanna try the DMOZ directory project to get ideas if you don't have any - link opens in a new window and they have a brand new mascot there, green mascot actually).

Thank you for reading and don't forget to like&share&subscribe to rss and you know...

Stay tuned for more on RolandC.net!


This article first appeared Thursday the 4th of August 2016 on RolandC.net.

Can't access your Hotmail pop email anymore? (24th June 2016)

Friday, June 24, 2016

Yesterday (23rd June 2016), I have spent all day noticing Thunderbird could not retrieve my Hotmail pop email anymore. The following dialog boxes were constantly shown


Hotmail pop sync problems on rolandc.net
 
Hotmail pop sync problems on rolandc.net


I had also noticed that Hotmail's Webmail interface got a new paint job that day, and somehow I knew this had to be connected.

Right, it was !

Even though I had been getting my Hotmail email from Thunderbird for years without encoutering a single issue, I just realized that the option to get Hotmail's pop email retrieved was now disabled by default!

So, here's what I found while visiting Hotmail's Webmail option page:


Hotmail pop sync problems on rolandc.net


Nice paint job huh?!

Just click on Yes and you will get your Hotmail email again as usual. No complicated two-step verification process involved or anything. That's as simple as that.

By the way, in case you can't figure out where the dialog box above came from, read on:

- Visit hotmail.com or I guess outlook.live.com is a shorter way here
- Click on the Settings menu (as found below)


Hotmail pop sync problems on rolandc.net


- Click on Options


Hotmail pop sync problems on rolandc.net

Then click on Mail -> Accounts -> POP and IMAP


Hotmail pop sync problems on rolandc.net


 Once there, don't forget to hit Save (a little above) and you're done.

I hope this helped you save the day by getting your customers email back in your email client as usual. Stay tuned for more on RolandC.net


This article first appeared Thursday the 16th of February 2016 on RolandC.net.

How to build your own Multilingual PHP MVC CMS from scratch - Part 6 - Language/Theme Handling

Tuesday, February 16, 2016

Welcome to the sixth settlement of our tutorial series on how to build your own Multilingual MVC CMS from scratch and in which I will get into full detail as to how themes and languages are managed in our project.

During the last settlement, I have demonstrated a way of building a user interface that allows us to register, sign in and review/update personal data. I have also challenged you, aided by all the knowledge you have gained so far, to try and build some additional features  like forgotten password, password changing and extra settings modules on your own project, operation that you should not be so difficult by now and that should considerably enhance the interface you have started to build, if you have.

But today, I will be getting in a totally different aspect of the project, gathering all the previously discussed elements about theme and language handling and coming full circle by giving you a complete, synthesized overview of the matter. I will point out however, that this will not have any direct relation with the back-end language and theme management features we will be starting to create during the next settlements, as this will in fact only be a discussion about everything pertaining to theme/language handling, mostly in front end.

Language handling


So, starting with languages and starting with the obvious, all language files, whether used in back-end or in front-end, will fall under the same directory structure as shown right bellow




The file named encoding.php merely holds an array containing charsets on a per language basis, we will later review the method it is being useful to.
 <?php  
   
      /* languages/encoding.php */  
   
      $encod = array(  
           'en' => 'utf-8',  
           'fr' => 'utf-8',  
           'uk' => 'utf-8',  
           'ru' => 'utf-8',  
           'de' => 'utf-8',  
           'nl' => 'utf-8',  
           'se' => 'utf-8',  
           'es' => 'utf-8'  
      );       
 ?>  

The file named translate.js contains a script that's needed to help localize all Javascript strings. Since I have already presented it in a previous article (http://www.sitepoint.com/localizing-javascript-strings-php-mvc-framework/), I will not need to do so here. 

Obvious is the fact that each language folder contains its own flag, in .png format.

One thing that differentiates the front-end section however, is that a template folder has been added to address template needs while sending out emails, eg: confirmation of registration, contact form and new password sending. Indeed, a user who has registered using Swedish may not like to receive a confirmation all written in English or in any language other than Swedish for that matter. 




Let's now take the example of registration_en.php
 <?php   
   
 /* languages/templates/en/registration_en.php */  
   
 $content = "  
 <html>  
 <head>  
 <title>Your registration</title>  
 </head>  
 <body>  
 <p>Thank you for your registration on \"".SITE_TITLE."\"!</p>  
 Your credentials are  
 <table>  
 <tr>  
 <td>Login : </td>  
 <th>".$login."</th>  
 </tr>  
 <tr>  
 <td>Pass : </td>  
 <th>".$password."</th>  
 </tr>  
 </table>  
 <p>  
 Regards,<br />   
 the site's team  
 </body>  
 </html>  
 ";  
 ?>  
Knowing HTML, you can all imagine what the output is going to look like in your favorite email client program. 

Interesting too is the constant named CONTACT_FORM_RECEIVER_LANGUAGE, which is used to define the language in which the administrator will like to receive its feedback emails.
 define("CONTACT_FORM_RECEIVER_LANGUAGE","en"); // the code of the language in which you wish to receive the user contact messages  
Now for the core language files that hold most of the PHP localized strings, let's take the example of en.php
 <?php  
   
 /* from languages/en/en.php */  
   
 $t_welcome = 'Welcome';  
 $t_contact = 'Contact';  
 $t_registration = 'Register';  
 $t_reg = 'Register';  
 $t_copyright = 'Copyright';  
 $t_submit = 'Submit';       
 $t_save = 'Save';  
 ...  
 ?>  
As an unspoken rule, localized PHP strings follow the format $t_my_word_or_sentence = 'My word or sentence'; Thus the variable name is always lowercase, each word separated by an underscore, and t_ is appended at the beginning of each term. HTML tags may be used inside the literal string part, constants too, but best is to avoid this whenever possible, for readability at least.

And now, time to get into the controversial debate about global variables. Since day 1, I have chosen to use global variables to access the translation strings we have just discussed. So, for example, throughout the whole site, we may access $t_welcome by using $GLOBALS['t_welcome'], which turns out to be greatly convenient, for a number of reasons.

Although... I can hear some of you screaming 'globals? DO NOT USE GLOBALS!', which is sometimes fair in its own right. But passed the fact that I myself am totally not convinced using globals for my translation system here will affect the project or anyone instantiating/using it, fact is, I have even yet to be convinced by the generally advised methods in the field, which are either about stacking everything into array from which translations are latter picked, or using POEdit/gettext which I don't feel truly answers the specific needs I have been having for this project so far. 

So that, the array method being pretty cumbersome to maintain and great only for small localization projects and also, with over 1000 translation strings for each language, unless someone comes up with a great new way to handle localization that's easily and quickly implementable, even in large-scale environments, that's the method I will keep using. Besides, not only this is the one method I am myself being the most comfortable with, this is also a method that neither ever failed me nor made waves somewhere else in the project since I have started implementing it. So, as programmers too sometimes say: 'Don't repair it if it ain't broken!'.

But the truth is, within our project, there is much more to language handling than mere string localization. 

Let's start from the localization table and then go on with the language selector bar 
 <?php       
 --  
 -- Table structure for table `localization`  
 --  
   
 CREATE TABLE `localization` (  
  `id_localization` smallint(6) NOT NULL,  
  `localization_name` varchar(3) NOT NULL,  
  `localization_flag` varchar(8) NOT NULL,  
  `localization_enabled` enum('no','yes') NOT NULL,  
  `localization_default` enum('no','yes') NOT NULL,  
  `localization_timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP  
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;  
   
 --  
 -- Dumping data for table `localization`  
 --  
   
 INSERT INTO `localization` (`id_localization`, `localization_name`, `localization_flag`, `localization_enabled`, `localization_default`, `localization_timestamp`) VALUES  
 (7, 'se', 'se.png', 'no', 'no', '2014-10-24 19:17:26'),  
 (8, 'es', 'es.png', 'no', 'no', '2014-10-24 19:17:26'),  
 (5, 'ru', 'ru.png', 'no', 'no', '2014-10-24 19:17:26'),  
 (6, 'nl', 'nl.png', 'no', 'no', '2014-10-24 19:17:26'),  
 (3, 'fr', 'fr.png', 'yes', 'yes', '2014-10-24 19:17:26'),  
 (4, 'uk', 'uk.png', 'no', 'no', '2014-10-24 19:17:26'),  
 (1, 'en', 'en.png', 'yes', 'no', '2014-10-24 19:17:26'),  
 (2, 'de', 'de.png', 'no', 'no', '2014-10-24 19:17:26'),  
 (10, 'dk', 'dk.png', 'no', 'no', '2014-10-24 19:17:26');  
   
 --  
 -- Indexes for dumped tables  
 --  
   
 --  
 -- Indexes for table `localization`  
 --  
 ALTER TABLE `localization`  
 ADD PRIMARY KEY (`id_localization`);  
   
 --  
 -- AUTO_INCREMENT for dumped tables  
 --  
   
 --  
 -- AUTO_INCREMENT for table `localization`  
 --  
 ALTER TABLE `localization`  
 MODIFY `id_localization` smallint(6) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=11;  
 ?>  
Nothing too unexplainable about this one, although it's a good occasion to remind you the timestamp field is here only to help force MySQL perform an update of at least one row every time we perform an operation but the data hasn't changed.

And from this table, we can start building a language selector bar users will be able to interact with, shifting from one language to another easily.
 <?php  
      /* from themes/your_style/main.php */  
   
      // we fetch an instance of the localization model   
      // and call the method that will retrieve all enabled languages  
      $lang_bar = LocModel::getInstance()->getData_language_bar();  
   
      foreach($lang_bar as $ln_item){//for each language  
   
           if(GRAPHICAL_LN_BAR == true){   
                // if flag view is set, we display the flag for each language  
                // wrapped in the target url  
                // The get_ln_straight() method from bellow  
                // will take in a URL and process it to match the language   
                // on which the mouse is hovering, 
                // all while preserving the other elements the current URL contains, as well as their order  
           ?>   
                <a href="<?php echo LocModel::getInstance()->get_ln_straight($ln_item[0]); ?>"><img src="<?php echo CLEAN_PATH.'/'; ?>languages/<?php echo $ln_item[0]; ?>/<?php echo $ln_item[1]; ?>" width="<?php echo FLAG_WIDTH; ?>" height="<?php echo FLAG_HEIGHT; ?>" alt="<?php echo isset($GLOBALS['t_'.$ln_item[0].'_lng'])?$GLOBALS['t_'.$ln_item[0].'_lng']:"$ln_item[0]"; ?>" /></a> <?php   
           }  
           else{   
           ?>  
                <a href="<?php echo LocModel::getInstance()->get_ln_straight($ln_item[0]); ?>"><u><?php echo $ln_item[0]; ?></u></a> <?php   
           }  
      }  
 }  
 ?>  
There is not much to write about the commented above code, expect for the call to the get_ln_straight() method, which will, no matter what the current URL is, make sure the language that's being displayed in the target link matches the flag/language name on which the mouse of the user is hovering, all while preserving the other elements the current URL contains, as well as their order. But before that, let's consider the useful getData_language_bar() method
 <?php  
   
      /* from application/models/LocModel.php */  
        
      ...  
      public function getData_language_bar(){  
   
           $this->locData = array();  
           $line = array();  
   
           // we first check what language directories (inside both front-end and back-end /language dirs)   
           //exist physically on server. We use PHP's glob() function for that  
           $language_directories = glob(PATH_TO_LANGUAGES . '/*' , GLOB_ONLYDIR);  
           $language_admin_directories = glob('admin/'.PATH_TO_LANGUAGES. '/*' , GLOB_ONLYDIR);  
   
           $admin_language_list = array();  
   
           foreach($language_admin_directories as $admin_dir){  
                $exp_admin_dir = explode("/",$admin_dir);  
                array_push($admin_language_list, end($exp_admin_dir));  
           }  
   
           $language_list = array();  
   
           foreach($language_directories as $dir){  
   
                $exp_dir = explode("/",$dir);  
   
                // if the currently processed front-end language dir can be found in the admin language dir array,  
                // we push it inside the $language_list array, to be used later for checking   
                if( in_array( end($exp_dir), $admin_language_list)){array_push($language_list, end($exp_dir));}  
           }  
   
           //then we pull out the language list from the db  
           $q_ln_bar = "SELECT id_localization, localization_name, localization_flag, localization_enabled   
                           FROM localization   
                           WHERE localization_enabled = 'yes'  
                           ORDER BY id_localization ASC                      
                          ";  
   
           MySQLModel::get_mysql_instance()->executeQuery($q_ln_bar);  
   
           while($mylnbar = MySQLModel::get_mysql_instance()->getRows($q_ln_bar)){  
        
                //we now compare the each freshly pulled language row from the DB   
                //with our array of check existing physical language dirs  
                if(in_array($mylnbar['localization_name'], $language_list)){   
                     // so that, basically, if the name of the currently processed language corresponds to existing language folders   
                     //in both back-end and front-end at the same time, we consider that language valid and stack it in an array,   
                     //that will be stacked with other arrays in one more array that will be returned to us as a whole   
                     array_push($line, $mylnbar['localization_name'],$mylnbar['localization_flag']);  
                     array_push($this->locData,$line);$line = array();  
                }  
           }  
           return $this->locData;   
      }       
      ...  
   
 ?>  
And then the get_ln_straight() method we have started to discuss earlier, here to ensure the user will always fall back on its feet while changing the language from any page
 <?php  
      /* from application/models/LocModel.php */  
        
      ...  
      public function get_ln_straight($lang){  
        
           //using $_SERVER['QUERY_STRING'] will make our life a lot easier when it comes to dealing with clean URLs  
        
           if(empty($_SERVER['QUERY_STRING'])){ // if $_SERVER['QUERY_STRING'] turns out empty we use default values  
                if(DEFAULT_PLINK){$plink = '&'.PLINK.'='.CURRENT_PLINK;} else{$plink = '';}  
                $_SERVER['QUERY_STRING'] = RLINK.'='.CURRENT_RLINK.$plink.'&'.LN.'='.CURRENT_LANG;  
           }   
   
           $this->query_string = $_SERVER['QUERY_STRING'];  
           $pat = '/'.LN.'=[a-z]{2}/';  
           $pat2 = '/language=[a-z]{2}/'; //that's for Google CSE  
   
           //if we can spot a valid URL language atom, we replace it by the then-sought language, $lang,   
           //initially passed as parameter to the method  
           if(preg_match($pat, $this->query_string, $matches, PREG_OFFSET_CAPTURE, 3)){  
                $rpl = LN.'='.$lang;  
                $this->query_string = preg_replace($pat, $rpl, $this->query_string);  
           }  
   
           if(preg_match($pat2, $this->query_string, $matches, PREG_OFFSET_CAPTURE, 3)){  
                $rpl2 = 'language='.$lang;  
                $this->query_string = preg_replace($pat2, $rpl2, $this->query_string);  
           }  
   
           //normally at that point, we should already be fine,  
           //but clean urls come complicating everything again...  
           if(CLEAN_URLS == true){//if clean urls are on,  
                  
                //we don't just preg_replace our query string, we explode it.  
                $exp_query_string = explode('&', $this->query_string);  
   
                $markers = array(  
                     //and here come markers  
                     //basically, markers are something we use with clean_urls whenever thought necessary,   
                     //to avoid collision between .htaccess rewrite rules, when there are (too) many of them  
                     'pn' => PGN,  
                     //note that 'pn' is what will be written in .htaccess  
                     //and PGN is the constant name that normally holds the page number url variable 'pgn'.   
                     // again, with clean urls being off, you could assign any other value than pgn to the constant PGN  
                     'sn' => SN  
                );  
   
                $fake_string = array(); //this will hold our new url string until imploded  
   
                foreach($exp_query_string as $atom){ //for each atom of the old string (x=y)  
   
                     $exp_atom = explode("=",$atom); // we explode it (the atom)  
   
                     $needle = $exp_atom[0];     //and take its left part, called the needle in this case  
   
                     if(!preg_match('/success/',$atom)){// we avoid the word success in our algorithm for collision reasons       
                            
                          //we then search for that needle inside the haystack (marker's array)  
                            
                          if(in_array($needle, $markers) ){   
                               //if found, we will create a new clean atom with the marker in it.   
                               //because that's what markers are here for - making clear what the nature of the value they bare is  
   
                               $key = array_search($needle, $markers); //so we get its key   
   
                               $new_atom = $key.'/'.$exp_atom[1]; //and actually create the new 'clean' atom using that key,   
                               //a slash sign and the value of the old one  
                          }  
                          else{$new_atom = $exp_atom[1];} // otherwise, if the atom does not need a marker, we retain only the x part of the old string  
   
                          array_push($fake_string,$new_atom); //and we stack each resulting atom in our $fake_string array  
                     }  
                }  
   
                ///and then we turn that array to a string, using '/' to implode all parts  
        
                $new_string = implode("/", $fake_string);  
                  
                //we quickly append our clean path constant and we're good to go  
                $this->query_string = CLEAN_PATH.'/'.$new_string;  
   
                //not just yet though... in case of Google CSE  
                if(preg_match('/cx=(.*)&gs=(.*)&sa.x=(.*)&sa.y=(.*)/', $_SERVER['REQUEST_URI'])){   
                     // so, if google search is on  
                     //we explode not the query string but the request URI this time  
                     $exp_q_s = explode('?',$_SERVER['REQUEST_URI']);  
                     $this->query_string .= '?'.$exp_q_s[1]; //and retain only the parts proper to Google,   
                     //(so the user keeps seeing its search results after it has selected another language),   
                     //we concatenate them to the query string we've just built and now we're good to go.  
                }  
           }   
           else{  
                //else, if clean urls weren't set, we only need to append $_SERVER['PHP_SELF']   
                //instead of the clean path constant before the new string we have built   
                $this->query_string = $_SERVER['PHP_SELF'].'?'.$this->query_string;  
           }  
   
           return $this->query_string; //we return our resulting string  
      }  
 ?>  
Then still from the localization model, the get_encoding_straight() method, which if passed the current language as parameter, will return the corresponding charset, as defined in languages/encoding.php. This method will be used in header.php, both in front-end and back-end. By the way, we will get to the header files today while getting in the subject of theme handling, so we will have a chance to see the get_encoding_straight() method in action there.  
 <?php  
      /* from application/models/LocModel.php */  
   
      ...  
      public function get_encoding_straight($lang){  
   
           require PATH_TO_LANGUAGES.'/encoding.php'; // we require the file that contains the $encod array, as discussed earlier today.  
   
           if (array_key_exists($lang,$encod)){ //if we can find the current language in that array, as key  
                $this->encoding = $encod[$lang]; we then use its corresponding value  
           }  
           else{$this->encoding = 'utf-8';} //else we default to utf-8  
   
           return $this->encoding; //we return what we have then  
      }  
 ?>  
Finally, a word about the choice of character sets though: while this may feel odd to some of you that I sometimes modify the MySQL charset to Latin1 before attempting a query, from the way the database tables for this project were originally set up (latin1_swedish_ci), plus using utf-8 as HTML charset on all pages, no encoding-related display issues have yet arisen while testing the system against languages such as French, Russian or even Swedish, this on both Windows/*nix platforms. In other words, why changing what already works? But still, if you somehow end up getting encoding issues that I myself do not get, just try other values to see what works for you, although, everyone should be just fine with the settings as they currently are, regardless of the (modern) browser it's being displayed in.

Theme handling


At the beginning of these series, I have presented the template engine that dynamically includes all the necessary view files, plus takes care off the passing of variables from the controller. But there's of course more to theme handling than this powerful routine. I justify:

Let's start by analyzing the overall structure of the theme folder




Then the same structure in more detail

 

From this picture above, you can see there are two different styles being operated, which are named skane and sparta. These folders exist in the back-end section too but vary in content.

At root (/themes/your_style), you can find all the view files proper to each module and not only. There lie our common template files header.php, main.php and footer.php and we can also find secondary view files like vf_image_u.php or follow.php, which respectively create an image for the captcha in registration.php and create a social media follow bar to be included wherever found stylish inside main.php. More interesting view files can be found there, like paging.php but we will get to them during the forthcoming settlements only.

Jumping straight into the css folder, we find name_of_the_style.css.php which is the main css file for any given theme. It bares a .php extension because on some occasions we need to use PHP variables there. We also find specific CSS files for the libraries we use like dTree, which has dtree.css sitting there and so on.

On the same level as the css folder, we find a folder called images. That folder holds two sub-folders called favicon and servicing. While it looks obvious what the favicon folder is here for, the servicing folder however requires a little explanation. It is a folder that holds images that will not only build on the visual identity of a given theme, but that are also dedicated to common navigation items. As an example, servicing is the folder where you will find small arrows, warning and missing page/module icons. And since menus are considered standard navigation items, you will also find two sub-folders called dtree and hmenu, which contain images for use with the menu system you already know of. In fact a third sub-folder to the servicing folder can even be found, called rss, which contains a few rss feed logo .png format images to choose from (see RSS_ICON_FILE in config.php).

At the root level of /themes/your_style/images, therefore at the same level where the favicon and servicing folder are, can main logo images be found.

Entering the /themes/your_style/css folder again, we find there's an image folder too, not to be confused with /themes/your_style/images. This will contain images for use in Javascript dialog boxes. Note these dialog boxes are also configurable via /themes/your_style/css/dialog_box.css.
 
Back at the root level of the themes folder reside two of our common template files. The first one is header.php
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
 <?php  
 // here we sort out language code names to be compatible with most standards  
 if(CURRENT_LANG){  
      if(CURRENT_LANG == 'uk'){$tinymce_lang = 'uk_UA';}  
      elseif(CURRENT_LANG == 'fr'){$tinymce_lang = 'fr_FR';}  
      elseif(CURRENT_LANG == 'se'){$tinymce_lang = 'sv_SE';}  
      else{$tinymce_lang = CURRENT_LANG;}  
 }  
 else{$tinymce_lang = 'en';} // if nothing was found, we default to English  
 ?>  
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $tinymce_lang; ?>" lang="<?php echo $tinymce_lang; ?>">  
 <head>  
 <?php   
 $header_utils = new UtilsModel();  
 $met = $header_utils->get_meta_info(); //we fetch meta infos from the DB, this is being discussed further down the page.  
   
 foreach($met as $m){ //for each meta entry...   
      if($m[0] == 'title'){ // if tag is title  
           echo '<title> ';   
           if(isset($page_name)){echo $page_name;}   
           else{  
                if(isset($module_name)){  
                     echo isset($GLOBALS['t_'.strtolower($module_name)]) ? $GLOBALS['t_'.strtolower($module_name)] : $module_name;  
                }  
           }  
           echo '</title>';   
           }  
      else{ //if not title tag, we use the rest of what we have pulled from the meta table.  
           if($m[0] == 'http-equiv'){   
                if($m[1] == "Content_Type"){$m[1] = "Content-Type";}   
                $char = " charset = ".LocModel::getInstance()->get_encoding_straight(CURRENT_LANG)."";  
                $m[2] .= $char;  
           }  
           echo '<meta '.$m[0].'="'.$m[1].'" content="'.$m[2].'" />';  
      }  
 }  
 ?>  
 <!-- now we link all the CSS/JS we need on our project -->  
 <link rel="shortcut icon" href="<?php echo CLEAN_PATH.'/'.PATH_TO_THEMES; ?>/<?php echo CURRENT_THEME; ?>/images/favicon/favicon.ico" type="image/x-icon" />  
 <link rel="stylesheet" href="<?php echo CLEAN_PATH.'/'.PATH_TO_THEMES; ?>/<?php echo CURRENT_THEME; ?>/css/<?php echo CURRENT_THEME; ?>.css.php" type="text/css" />  
 <link rel="stylesheet" type="text/css" href="<?php echo CLEAN_PATH.'/'.PATH_TO_THEMES; ?>/<?php echo CURRENT_THEME; ?>/css/dtree.css" />  
 <link rel="stylesheet" type="text/css" href="<?php echo CLEAN_PATH.'/'.PATH_TO_THEMES; ?>/<?php echo CURRENT_THEME; ?>/css/hmenu.css" />  
 <link rel="stylesheet" type="text/css" href="<?php echo CLEAN_PATH.'/'.PATH_TO_THEMES; ?>/<?php echo CURRENT_THEME; ?>/css/dialog_box.css" />  
 <link rel="stylesheet" type="text/css" href="<?php echo CLEAN_PATH.'/'.PATH_TO_THEMES; ?>/<?php echo CURRENT_THEME; ?>/css/dtp.css" />  
 <script src="<?php echo CLEAN_PATH.'/'; ?>languages/<?php echo CURRENT_LANG; ?>/JS/<?php echo CURRENT_LANG; ?>.js" type="text/javascript"></script>  
 <script src="<?php echo CLEAN_PATH.'/'; ?>languages/translate.js" type="text/javascript"></script>  
 <?php  
 include 'public/js/main.php';  
 include 'public/js/dtree.php';  
 ?>  
 <script src="<?php echo CLEAN_PATH.'/'; ?>/public/js/hmenu.js" type="text/javascript"></script>  
 <?php include 'public/js/dialog_box.php'; ?>  
 <script src="<?php echo CLEAN_PATH.'/'; ?>public/js/dtp.js"type="text/javascript"></script>  
 <!-- and in case Javascript is disabled, we use a noscript tag -->  
 <noscript><div class="disabled_feature"><?php echo $GLOBALS['t_javascript_has_been_disabled']; ?> <?php echo $GLOBALS['t_please_reenable_it']; ?> - <a href="http://www.enable-javascript.com" target="_blank"><u><?php echo $GLOBALS['t_instructions_here']; ?></u></a></div>  
 </noscript>  
 </head>  
So, in header.php, we first sort out our language code names, for enhanced compatibility with the most standards officially in use and we use the freshly defined code in our html xml declaration. Then we pull all meta info from the DB table called meta, that looks as follows
 --  
 -- Table structure for table `meta`  
 --  
   
 CREATE TABLE `meta` (  
  `id_meta` tinyint(4) NOT NULL,  
  `meta_type` varchar(12) NOT NULL,  
  `meta_name` varchar(40) NOT NULL,  
  `meta_value` text NOT NULL  
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;  
   
 --  
 -- Dumping data for table `meta`  
 --  
   
 INSERT INTO `meta` (`id_meta`, `meta_type`, `meta_name`, `meta_value`) VALUES  
 (1, 'title', 'title', 'Gumbo-CMS'),  
 (2, 'name', 'description', 'Gumbo (or Gumbo-CMS) is a Mutilingual MVC CMS written \'from scratch\' in PHP 5 and that requires Apache 2.2+ and MySQL 5.6+'),  
 (3, 'name', 'keywords', 'cms, mvc, php, multilingual cms'),  
 (4, 'name', 'robots', 'index, follow'),  
 (5, 'name', 'google_bot', 'index, follow'),  
 (6, 'name', 'google', 'notranslation'),  
 (7, 'http-equiv', 'Content_Type', 'text/html;');  
   
 --  
 -- Indexes for dumped tables  
 --  
   
 --  
 -- Indexes for table `meta`  
 --  
 ALTER TABLE `meta`  
  ADD PRIMARY KEY (`id_meta`);  
   
 --  
 -- AUTO_INCREMENT for dumped tables  
 --  
   
 --  
 -- AUTO_INCREMENT for table `meta`  
 --  
 ALTER TABLE `meta`  
  MODIFY `id_meta` tinyint(4) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8;  
and we start looping the results we have successfully fetched.

A note about meta tags though, please visit https://support.google.com/webmasters/answer/79812?hl=en for a brief overview of the tags Google Inc. understands.

Then we go onto including all needed CSS/Javascript files and end up using a noscript tag that will warn users in case Javascript is disabled on their viewing device. That's all there is to the header file found in the front-end. In a next settlement though, we will see the header file for the back-end contains a lot more information, primarily due to TinyMCE being initialized there. Note that header.php ends with the closing head tag. 

Meanwhile, in footer.php, the starting tag is a tr tag and the closing tag is the closing html tag
 <?php  
      /* themes/your_style/footer.php */  
 ?>  
      <tr class="footer_tr">  
           <td colspan="2" class="footer">  
                <?php echo $GLOBALS['t_copyright'].' '.COPYRIGHT; ?>  
           </td>  
      </tr>  
 </table>  
 </div>  
 </body>  
 </html>  
Nothing too difficult about this one as well, and that wraps up this settlement in which you have learned more about the way languages and themes are dealt with in this project, getting for now a complete overview, for at least the front-end part.

But next time, I will take these series to yet another level, getting onto the other side of the fence by introducing you to the back-end side of our CMS, starting with the building of a really exciting language management module. Creating this new module will allow me, among other things, to tell you more about the paging and sorting features many of the back-end modules actually depend on.

This article first appeared Thursday the 16th of February 2016 on RolandC.net.

How to build your own Multilingual PHP MVC CMS from scratch - Part 5 - Frontend (part 3): User Interface

Tuesday, December 29, 2015

Welcome to the fifth settlement of our tutorial series on how to build your own Multilingual MVC CMS from scratch and in which we will be building an interface for the user to sign in and update its personal data.

During the last settlements, we have created a page module that allows the user to display a specific page based on URL input and aided by some third-party scripts, we have also created a customizable menu display system that comes in two layout flavors, vertical and horizontal. In a future settlement, I will show you how to create the back-end feature that will allow you to completely edit the menus stored in the database.

But for now, we will set our interest onto building a user interface that will allow for signing into the system as well as updating personal data. Now, since in reality, the user’s experience on this project encompasses at least 6 whole modules, which are: registration, sign in, forgotten password, account, profile, settings, password changing, plus the use of authentication model as well and that we obviously cannot review all of them at once, we will only be reviewing some of them, which are the registration, signin and profile modules. And this will still be a lot, so I will be focusing only on the parts that are of interest. This makes sense since there is no point in explaining how a controller works over and over again.

Anyway, the table named ‘users’ represents the natural entry point through which we will start digging our user interface.
 --  
 -- Table structure for table `users`  
 --  
   
 CREATE TABLE `users` (  
  `id_user` int(11) NOT NULL,  
  `user_login` varchar(12) NOT NULL,  
  `user_password` text NOT NULL,  
  `user_email` varchar(72) NOT NULL,  
  `user_country` varchar(255) NOT NULL DEFAULT 'uk',  
  `user_gender` enum('else','female','male') NOT NULL,  
  `user_birthdate` varchar(100) NOT NULL,  
  `user_type` enum('0','1','2') NOT NULL,  
  `user_ip` varchar(16) NOT NULL,  
  `user_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;  
   
 --  
 -- Dumping data for table `users`  
 --  
   
 INSERT INTO `users` (`id_user`, `user_login`, `user_password`, `user_email`, `user_country`, `user_gender`, `user_birthdate`, `user_type`, `user_ip`, `user_timestamp`) VALUES  
 (1, 'test', 'c08ac56ae1145566f2ce54cbbea35fa3', 'test@mail.com', 'fidji', 'male', '08/02/1985', '1', '127.0.0.1', '2015-10-02 00:55:41');  
   
 --  
 -- Indexes for dumped tables  
 --  
   
 --  
 -- Indexes for table `users`  
 --  
 ALTER TABLE `users`  
  ADD PRIMARY KEY (`id_user`);  
   
 --  
 -- AUTO_INCREMENT for dumped tables  
 --  
   
 --  
 -- AUTO_INCREMENT for table `users`  
 --  
 ALTER TABLE `users`  
  MODIFY `id_user` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;  
Relying on the users table is the table named user_settings, which I will expose while discussing the profile module. Note the user_type field will not be of much help since permission levels will not be implemented, at least not until the end of the series.

Without any transition whatsoever, let’s jump right in the registration form. There’s no point in providing a user with an interface to sign in if that user was never given the opportunity to register beforehand, right?
 /* themes/your_style/registration.php */  
   
 <div align="center">  
 <?php  
 // we first take care off displaying feedback, if it contains error messages  
 if(isset($feedback) && !is_string($feedback)){  
 ?>  
      <table border="0" cellpadding="2" cellspacing="2" align="center">  
      <ul>  
      <?php  
      foreach($feedback as $err){  
           foreach($err as $er){  
                $msgs = '';  
                $exp_msg = explode("#",$er[1]);  
                $cx = 0;  
                foreach($exp_msg as $em){  
                     if($cx < sizeof($exp_msg)-1){$trailer = '; ';}else{$trailer = '';}  
                     $msgs .= $GLOBALS['t_'.$em].$trailer;$cx++;  
                }  
                ?>  
                <tr><td><div class="response_negative" style="font-size:12px;"><li><?php echo ucfirst($GLOBALS['t_'.$er[0]]); ?> -> <?php echo $msgs; ?></div></li></td></tr>  
      <?php   
           }  
      }  
 ?>  
      </ul>  
      </table>  
      <br />  
      <?php  
 }  
 if(isset($feedback) && $feedback == 'failed'){ ?><div class="failed"><?php echo $GLOBALS['t_problem_registering_you']; ?></div><?php }  
   
 //now preparing for action urls to deploy  
 if(CLEAN_URLS == true){$url = CLEAN_PATH.'/'.CURRENT_RLINK.'/'.CURRENT_LANG;}  
 else{$url = $_SERVER['PHP_SELF'].'?'.RLINK.'='.CURRENT_RLINK.'&'.LN.'='.CURRENT_LANG;}  
   
 $utils = new UtilsModel();  
 $user_ip = $utils->get_user_ip();  
 ?>  
 <br /><br />  
 <p>&nbsp;</p>  
 </div>  
   
 <!-- the actual registration form -->  
 <form id="frm_registration" name="frm_registration" enctype="multipart/form-data" method="post" action="<?php echo $url; ?>" onsubmit="return Check_registration(this,'off');">  
       
 <table border="0" cellpadding="2" cellspacing="0" align="center">  
      <tr>  
           <td align="left" class="std"><?php echo $GLOBALS['t_login']; ?>*</td>  
           <td align="left" class="input"><input class="text" type="text" id="login" name="login" value="<?php echo isset($_POST['login'])?htmlspecialchars($_POST['login']):""; ?>" maxlength="<?php echo MAX_CHARS_LOGIN; ?>" onkeyup="check_login_exists();" /></td>  
           <!-- pay attention to the ternary statements - if post_login is set we use it,   
           //otherwise we leave the field empty - this allows for field rememberance,   
           //so that if for any reason the form gets reloaded, the user won't have to type everything over again -->  
      </tr>  
   
      <tr>  
           <td align="left" class="std"><?php echo $GLOBALS['t_password']; ?>*</td>  
           <td align="left" class="input"><input class="text" type="password" id="password" name="password" value="<?php echo isset($_POST['password'])?htmlspecialchars($_POST['password']):""; ?>" maxlength="<?php echo MAX_CHARS_PWD; ?>" /></td>  
      </tr>  
   
      <tr>  
           <td align="left" class="std"><?php echo $GLOBALS['t_email']; ?>*</td>  
           <td align="left" class="input"><input class="text" type="text" id="email" name="email" value="<?php echo isset($_POST['email']) ? htmlspecialchars($_POST['email']):""; ?>" maxlength="<?php echo EMAIL_MAX_LENGTH; ?>" onkeyup="check_email_exists();" /></td>  
      </tr>  
   
      <tr>  
           <td align="left" class="std"><?php echo $GLOBALS['t_country']; ?>*</td>  
           <td align="left" class="input">  
           <select name="country">  
           <?php   
           require('lists/countries/countries.php'); //for clarity, we use a country list  
   
                if(isset($country_list) && !empty($country_list)){  
   
                foreach($country_list as $country){                      
                     if(isset($_POST['country']) && $_POST['country'] == $country[1]){$sel = ' selected=selected';}else{$sel = '';}  
                ?>  
                     <option value="<?php echo $country[1]; ?>"<?php echo $sel; ?>><?php echo $country[0]; ?></option>  
                     <?php  
                     }  
                }   
           ?>  
           </select>  
           </td>  
      </tr>  
   
      <tr>  
           <td align="left" class="std"><?php echo $GLOBALS['t_gender']; ?>*</td>  
           <?php  
 // if(isset($_POST['gender'])){echo $_POST['gender'];$checked = ' checked';}else{$checked = '';}   
 ?>  
           <td align="left" class="input">  
                <table border="0" cellpadding="2" cellspacing="0" width="100%">  
                     <tr>  
                          <td><input type="radio" id="gender" name="gender" value="male" checked />&nbsp;&nbsp;<?php echo $GLOBALS['t_male']; ?></td>  
 </tr><tr>  
   
                     <td><input type="radio" id="gender" name="gender" value="female"<?php if(isset($_POST['gender']) && $_POST['gender'] == 'female'){echo ' checked';} ?> />&nbsp;&nbsp;<?php echo $GLOBALS['t_female']; ?></td>  
                     </tr>  
                </table>  
           </td>  
      </tr>  
   
      <tr>  
           <td align="left" class="std"><?php echo $GLOBALS['t_birthdate']; ?>*</td>  
           <td align="left" class="input"><input class="text" type="text" id="birthdate" name="birthdate" value="<?php echo isset($_POST['birthdate'])?htmlspecialchars($_POST['birthdate']):""; ?>" maxlength="10" onkeyup = "displayDatePicker('birthdate');" onclick = "displayDatePicker('birthdate');" /></td>  
      </tr>  
        
      <tr>  
           <td align="left" class="std"><?php echo $GLOBALS['t_newsletter']; ?></td>  
           <td align="left" class="input"><input type="checkbox" name="newsletter" value=""<?php if(isset($_POST['newsletter'])){$checked = ' checked';}else{$checked = '';} echo $checked; ?> /></td>  
      </tr>  
   
      <tr align="right">  
           <td align="center" valign="middle" class="std" valign="bottom"><?php echo $GLOBALS['t_verification_code']; ?></td>  
           <td align="center">  
                <table border="0" cellpadding="0" cellspacing="0" width="100%">  
                     <tr align="center">  
                          <td valign="middle"><img src="<?php echo CLEAN_PATH.'/'.PATH_TO_THEMES.'/'.CURRENT_THEME.'/vf_image_u.php'; ?>" />&nbsp;</td>  
                          <td><input name="vercode_u" type="text" id="vercode_u" autocomplete="off" maxlength="5" style="height:;width:155px;font-size:px;background-color:#;color:#;" /></td>  
                     </tr>  
                </table>  
           </td>  
      </tr>  
   
      <tr>  
           <td><input type="hidden" name="type" value="1" /></td>  
           <td><input type="hidden" name="ip" value="<?php echo $user_ip; ?>" /></td>  
      </tr>  
   
      <tr>  
           <td class="std" colspan="2" align="center">  
           <br />  
           <input type="submit" id="submit" name="submit" class="submit" value="<?php echo $GLOBALS['t_submit']; ?>" /></td>  
      </tr>  
 </table>  
 </form>  
There really isn’t much to the above code. It starts off displaying feedback messages, if those are containing registration errors. Then the actual registration form begins.

Ternary syntax is used for field remembering. Eg:
 <input class="text" type="text" id="login" name="login" value="<?php echo isset($_POST['login'])?htmlspecialchars($_POST['login']):""; ?>" maxlength="<?php echo MAX_CHARS_LOGIN; ?>" />  
This tells that if a $post login is set, let’s use it, otherwise let’s leave empty.

Client-side Javascript validation is operated by a call to the Javascript Check_registration() function that can be found in public/js/main.php. In the event that the user disables Javascript, server-side validation is triggered via the check_form_errors() method as discussed further bellow.

During both onkeyup and onclick events, a dynamic Javascript/CSS-written date picker library is instantiated and appears below the birthdate field. This library will provided be as an attachment along with the corresponding CSS file. You may choose not to use this library and it will make no difference in terms of validation, as the birthdate field is validated client-side as well as server side. This third-party open-source date picker is here to operate only as an aesthetically pleasant date picker, enhancing the overall visual aspect.

A country list file and script is used for country selection.
 <?php  
   
 /* lists/countries/coutries.php */  
   
 require('array_countries.php');  
 $country_list = array();  
 $line = array();  
   
 foreach($array_country_list as $c){  
      array_push($line, $GLOBALS['t_'.strtolower($c)], strtolower($c));  
      array_push($country_list, $line);  
      $line = array();  
 }  
 asort($country_list);  
 ?>  
This snippet is pretty much self-explanatory and draws from the array bellow to create a new array containing the internationalized name of each country along with the all-lowercase formatted name of that same country.

Note the alphabetical sorting operated by the asort() function does not work too well with languages like Russian, or with html ascii codes because as of now, that’s as far as PHP’s support in that matter goes.
 <?php  
   
 /* lists/coutries/array_countries.php */  
   
 $array_country_list = array(  
 'Afghanistan',  
 'Albania',  
 'Algeria',  
  ...,  
 'Zimbabwe'  
 );  
 ?>  
The full array is available as attachment, along with the English translations, to be added to languages/en/en.php

An image containing a verification code is created by vf_image_u.php using GD, as follows
 <?php  
   
 /* themes/your_style/vf_image_u.php */  
   
 session_name('general_purpose');  
 session_start(); //we will store our random number inside a session variable   
      //so we need to declare a session here too.  
   
 $text = rand(10000,99999);   
 $_SESSION["vercode_u"] = $text;   
 $height = 15;   
 $width = 45;   
    
 $image_p = imagecreate($width, $height); // check about.php in case the image does not display  
 $black = imagecolorallocate($image_p, 165, 174, 112);   
 $white = imagecolorallocate($image_p, 68, 68, 68);   
 $font_size = 14;   
    
 imagestring($image_p, $font_size, 0, 0, $text, $white);   
 imagejpeg($image_p, null, 80);   
 ?>  
This simple script is using the imagecreate() function from the GD library. If the image does not display, load the about.php script that resides at root, to find out if you have GD enabled + jpeg support. We also fabricate a random number that we store inside $_SESSION[“vercode_u”].

Of course other fields can be added, some removed, that’s up to you and your project’s business logic of course, but then you need to make sure the proper fields are correctly retrieved while processing the sent data inside RegModel.php.

But from a controller point of view, this has no importance whatsoever since we will fetch our form variables programatically. Here’s how
 <?php  
   
 /* application/controllers/RegController.php */  
   
 class RegController extends BaseController{  
        
      private $module_name;  
      private $form_name;  
      private $feedback;  
                  
      public function initialize(){  
             
           $this->registry->template->module_name = $this->getModuleName();  
           $this->registry->template->form_name = $this->getFormName();  
             
           // new in our initialize() method, the intercept_data() method  
           // which will catch all form variables   
           // and send it to the relevant model method  
   
           $this->registry->template->feedback = $this->intercept_data();  
           $this->registry->template->assign_theme();  
      }  
   
      public function getModuleName(){  
           $model = RegModel::getInstance();  
           return $model->get_module_name();  
      }  
        
      public function getFormName(){  
           $model = RegModel::getInstance();  
           return $model->get_form_name();  
      }  
        
      public function intercept_data(){  
   
           // this method is where we intercept form variables  
           // to be then sent to the relevant model's method  
   
           $this->dol_posts = array();       
   
           foreach($_POST as $key => $val){//for each form variable  
   
                     // if there's a $post set of that variable,  
                     // we stack its value in the dol_posts array  
                  
                if(isset($_POST[$key])){array_push($this->dol_posts,$key);}  
           }  
             
           $model = RegModel::getInstance(); //we call an instance of the reg model  
           return $model->get_form_feedback($this->dol_posts); // and invoke the method that will process our form variables, passing them as parameter.  
      }  
 }  
 ?>  
So, this controller brings us new elements that weren’t in the page controller because the logic there was different, there was no form and thus no data to be intercepted. The intercept_data() method is where we intercept form variables to be then sent to the relevant model’s method, in this case, the get_form_feedback() method belonging to the RegModel class (Singleton).
 <?php   
   
 /* from application/models/RegModel.php */  
   
 public function get_form_feedback($parms){  
             
           $util = new UtilsModel();  
   
           if(!empty($parms)){ //if we did receive form variables  
             
                $error_list = array();  
   
                // we check for form errors  
                $error_check_reg = self::check_reg_form_errors($parms);  
                //and whether login and email are unique  
                $is_unique_login = $util->check_if_unique('users', 'user_login', $_POST[$parms[0]], array('login','already_in_use'));  
                $is_unique_email = $util->check_if_unique('users', 'user_email', $_POST[$parms[2]], array('email','already_in_use'));  
   
                //if login is not unique, we stack that info into the error array  
                if (sizeof($is_unique_login) > 0){array_push($error_list,$is_unique_login);}  
   
                //same for email  
                if (CHECK_EMAIL_UNIQUE == true && sizeof($is_unique_email) == 1){array_push($error_list,$is_unique_email);}  
   
                //and for general form errors  
                if (sizeof($error_check_reg) > 0){array_push($error_list,$error_check_reg);}  
   
                //if there's at least one error found, no matter what type            
                if(sizeof($error_list) > 0){  
                     //we return our array of errors  
                     $this->form_feedback = $error_list;            
                }  
                else{  
                     //otherwise, no form errors, so we start processing the form   
                       
                     // that's the place where you make sure the form's HTML element   
                     // names you need to pull back correspond to what's in the list just bellow  
                     $field_list = array('login','password','email','country','gender','birthdate','newsletter','vercode_u','type','ip');  
                  
                     foreach($field_list as $fl){ //and for each for variable we have fetched,   
                          //if it can be found in the array above and there's a set $post variable of that name, then  
                          //we set a variable that will bare that same name, using the variable of variable ($$) syntax  
                          if(in_array($fl,$parms) && isset($_POST[$fl])){$$fl = $_POST[$fl];}  
                     }  
   
                     //building a query to insert the new user  
                     $q_insert_user = "INSERT INTO users (user_login, user_password, user_email, user_country, user_gender, user_birthdate, user_type, user_ip) VALUES ('$login', md5('$password'), '$email', '$country', '$gender', '$birthdate', '$type', '$ip')";  
   
                     // if the query executes well  
                     if(MySQLModel::get_mysql_instance()->executeQuery($q_insert_user) == 0){  
                          //we get the ID of the newly created user  
                          $q_id = "SELECT id_user, user_login,user_password FROM users WHERE user_login = '".$login."' AND user_password = md5('".$password."')";  
                          MySQLModel::get_mysql_instance()->executeQuery($q_id);  
                          $my_id = MySQLModel::get_mysql_instance()->getRows($q_id);  
   
                          if($my_id != ''){  
                               // and if everything went well and we have that ID we wanted  
                               $id = $my_id['id_user'];  
                       
                               //let's create a settings entry in db  
                               if(CURRENT_LANG){$this->lng = CURRENT_LANG;}  
                               //do not worry about the newsletter parameter because newsletters have not really been implemented  
                               //on the project. This is here only to give an effect of form completeness  
                               if(isset($newsletter)){$this->nl = 'yes';}else{$this->nl = '';}  
                            
                               //we build the query to insert a settings row for that new user  
                               $q_insert_user_settings = "INSERT INTO user_settings(user_settings_id_user, user_settings_language, user_settings_newsletter) VALUES ('$id','$this->lng','$this->nl')";  
                                      
                               if(MySQLModel::get_mysql_instance()->executeQuery($q_insert_user_settings) == 0){  
   
                                         //and if everything went according to plan, we authentify the new user                      
                                         $auth = AuthModel::getInstance();  
                                         $auth->stay_in($id,$login,$password);  
   
                                         //and then we take care of sending a 'thanks for registering - here are your credentials...' email  
                                         // we will discuss email sending via PHPMailer in a future settlement   
                                         //so you can safely skip that part for now.  
                                         require_once('languages/templates/'.CURRENT_LANG.'/'.strtolower($this->module_name).'_'.CURRENT_LANG.'.php');  
   
                                         if($regmail = $util->send_mail(CONTACT_FORM_EMAIL,$email,$GLOBALS['t_reg_email_subject'],$GLOBALS['t_reg_email_alt'],$content)){$this->form_feedback = 0 ;}  
   
                                         //In any case, our business logic requires us to relaod the page if registration turns out successful   
                                         //simply because we want the new user to automatically see a thanks for   
                                         //registering message,  
                                         //so we build a header location link and redirect the new user to that  
                                         if(DEFAULT_PLINK != ''){  
                                              if(CLEAN_URLS == true){$plink = '/'.DEFAULT_PLINK;}  
                                              else {$plink = '&'.PLINK.'='.DEFAULT_PLINK;}  
                                         }  
                                         else{$plink = '';}  
   
                                         if(CLEAN_URLS == true){$location = CLEAN_PATH.'/'.DEFAULT_RLINK.$plink.'/'.CURRENT_LANG.'/regs';}  
                                         else{$location = $_SERVER['PHP_SELF'].'?'.RLINK.'='.DEFAULT_RLINK.$plink.'&'.LN.'='.CURRENT_LANG.'&reg=success';}  
                                         header('Location: '.$location);exit();  
                                    }  
                               }  
                               else{$this->form_feedback = 'failed';} //self-explanatory  
                          }  
                               else{$this->form_feedback = 'failed';}  
                     }  
                }   
           return $this->form_feedback;  
      }  
 ?>  
Very simply, what that method does is, if we did intercept our form variables, we subject them to an error checking method (that we will review further bellow), we also check for the uniqueness of the login and email fields. If errors are found we stack them into an array we return to the controller, otherwise that means all came to us clean and we then start processing that data.

We insert the new user into the database and if the query went okay, we fetch its ID and insert a row into the user_settings table for that user. We log the user in, using our AuthModel class. We do send a confirmation email, but since we will discuss email sending in a future settlement only, we leave out that part for now. And since we want the new user to get a welcome message and everything, we build a location URL through which we will reload the page.

We will take a moment to review three things though, the first one being the check_reg_form_errors() method, useful to the get_form_feedback() method, as discussed above
 <?php   
   
 /* from application/models/RegModel.php */  
   
 public function check_reg_form_errors($mr){  
           $this->reg_form_errors = array();  
   
           $line = array();            
             
           foreach($mr as $r){  
   
                if(preg_match('/login/',$r)){  
                     if(!preg_match('/^[a-z0-9_]{'.MIN_CHARS_LOGIN.','.MAX_CHARS_LOGIN.'}$/',$_POST[$r])){  
                     array_push($line,'login','not_valid');array_push($this->reg_form_errors,$line);}  
                     // as an example, 'login' and 'not_valid' are the strings we will match against   
                     //while in registration.php to help us know the encountered format for the login field is not valid  
                }  
   
                if(preg_match('/password/',$r)){  
                     if(!preg_match('/^[a-z0-9_]{'.MIN_CHARS_PWD.','.MAX_CHARS_PWD.'}$/',$_POST[$r])){  
                          array_push($line,'password','not_valid');array_push($this->reg_form_errors,$line);  
                     }       
                }  
   
                if(preg_match('/email/',$r)){  
                     if (!filter_var($_POST[$r], FILTER_VALIDATE_EMAIL)){array_push($line,'email','not_valid');array_push($this->reg_form_errors,$line);}  
                     if($_POST[$r] != '' && mb_strlen($_POST[$r]) > EMAIL_MAX_LENGTH){array_push($line,'email','not_valid');array_push($this->reg_form_errors,$line);}   
                }  
   
                if(preg_match('/birthdate/',$r)){  
                     if(!empty($_POST[$r])){  
                          if(preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/',$_POST[$r])){$exp_d = explode('/',$_POST[$r]);  
                          //var_dump($exp_d);  
                          if(checkdate($exp_d[0], $exp_d[1], $exp_d[2]) != 1 || !preg_match('/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/',$_POST[$r])){array_push($line,'birthdate','not_valid');array_push($this->reg_form_errors,$line);}                                     
                          }  
                          else{array_push($line,'birthdate','not_valid');array_push($this->reg_form_errors,$line);}  
                     }  
                     else{array_push($line,'birthdate','not_valid');array_push($this->reg_form_errors,$line);}  
                }  
   
                if(preg_match('/vercode_u/',$r)){  
                     if(!preg_match('/^'.$_SESSION["vercode_u"].'$/',$_POST[$r])){array_push($line,'verification_code','not_valid');array_push($this->reg_form_errors,$line);}  
                }  
   
      $line = array();  
           }  
                //var_dump($this->reg_form_errors);  
           return $this->reg_form_errors;  
      }  
 ?>  
Nothing special on that one, as it’s only about basic preg_match verification of field format. Not however that, the arrays we send back in case of found errors, correspond to the expected arrays we expect and parse in registration.php, before we display the registration form.

Second, to be able log the new user in following successful registration, we have used the stay_in() method of yet another Singleton class, the authentication class called AuthModel
 <?php   
   
 /* from application/models/AuthModel.php */  
   
 public function stay_in($id, $user, $pwd, $lang = NULL){  
      $_SESSION['c_id'] = $id;  
      $_SESSION['c_login'] = $user;  
      $_SESSION['c_pwd'] = md5($pwd);  
      $_SESSION['c_lang'] = $lang;  
 }  
 ?>  
There, we simply store obviously needed credentials into session variables, so the user stays logged in as long as it still hasn’t logged out.

Third, the get_user_ip() method from UtilsModel.php that’s being called in the registration form from registration.php
 <?php  
   
 /* from application/models/UtilsModel.php */  
   
 public function get_user_ip(){  
             
      if (!empty($_SERVER["HTTP_CLIENT_IP"])){$this->real_ip = $_SERVER["HTTP_CLIENT_IP"];}  
      elseif(!empty($_SERVER["HTTP_X_FORWARDED_FOR"])){$this->real_ip = $_SERVER["HTTP_X_FORWARDED_FOR"];}  
      else{$this->real_ip = $_SERVER["REMOTE_ADDR"];}  
             
       return $this->real_ip;  
 }  
 ?>  
Nothing complicated here, this snippet will go as far as it can to fetch the real IP of the user. Fetching someone’s IP is no perfect science, so bare with me and with this code until someone comes up with something even more efficient. Although, this here should get a fair share of users hiding behind proxies. But you got my point, it won’t be perfect in any case.

So anyway, our user can already register and upon successful registration gets logged in automatically. Now what if that same user has logged out but needs to log in again? Our system must offer that user a way to manually log in to the system. This is where the Signin module comes in handy. But again and for the sake of clarity, I will only be showing the relevant pieces of code. Indeed, the SignIn controller is identical to the RegController, except for a few occurrences where ‘Reg’ should be replaced by ‘SignIn’ and that’s all there’s is to it. So we will now get straight into the signin model’s get_form_feedback() method.
 <?php  
   
 /* from application/models/SignInModel.php */  
   
 public function get_form_feedback($parms){  
             
      if(!empty($parms)){ //if we did get our form values  
             
           $error_list = array();  
   
           //we check them against the check_signin_form_errors() method  
           $error_check_pwdc = self::check_signin_form_errors($parms);  
   
           if (sizeof($error_check_pwdc) > 0){array_push($error_list,$error_check_pwdc);}  
             
           if(sizeof($error_list) > 0){$this->form_feedback = $error_list;} // if errors were found, we return them  
           //otherwise, we authenticate the user, invoking AuthModel  
           else{$this->form_feedback = self::authentify($_POST[$parms[0]],$_POST[$parms[1]]);}  
           //0 for login and 1 for password  
      }  
   
      return $this->form_feedback;  
 }  
 ?>  
Basically here, we validate our form data against the check_signin_form_errors() model, which looks as follows
 <?php  
   
 /* from application/models/SignInModel.php */  
   
 public function check_signin_form_errors($mr){  
           $this->signin_form_errors = array();  
   
           $line = array();            
           //var_dump($mr);  
             
           foreach($mr as $r){  
           //var_dump($r);  
                if($r == 'login'){  
                     if(!preg_match('/^[a-z0-9_]{'.MIN_CHARS_LOGIN.','.MAX_CHARS_LOGIN.'}$/',$_POST[$r])){  
                          array_push($line,'login','not_valid');array_push($this->signin_form_errors,$line);  
                     }       
                }  
   
                if($r == 'password'){//echo $_POST[$r];  
                     if(!preg_match('/^[a-z0-9_]{'.MIN_CHARS_PWD.','.MAX_CHARS_PWD.'}$/',$_POST[$r])){  
                          array_push($line,'password','not_valid');array_push($this->signin_form_errors,$line);  
                     }       
                }  
   
      $line = array();  
           }  
   
           return $this->signin_form_errors;  
      }  
 ?>  
This method acts exactly the same way as check_reg_form_errors(). But if everything goes okay, we authenticate the user by calling the authentify() method from the same class.
 <?php   
   
 /* from application/models/SignInModel.php */  
   
 public function authentify($login,$password){  
      $auth = AuthModel::getInstance();  
      $this->authentified = $auth->identify($login,$password);  
   
      return $this->authentified;  
 }  
 ?>  
This method called for the identify() method kept in AuthModel.php, which looks just like this bellow
 <?php  
   
 /* from application/models/AuthModel.php */  
   
 public function identify($user,$pwd){  
             
           $default_plink = '';  
   
           if(DEFAULT_PLINK){  
                if(CLEAN_URLS == true){$default_plink = DEFAULT_PLINK.'/';}  
                else{$default_plink = '&'.PLINK.'='.DEFAULT_PLINK;}  
           }  
             
          $q_auth = "SELECT id_user,user_login,user_password   
                     FROM users   
                     WHERE user_login = '".$user."'   
                     AND user_password = md5('".$pwd."')  
                ";  
   
           MySQLModel::get_mysql_instance()->executeQuery($q_auth);  
           $my_auth = MySQLModel::get_mysql_instance()->getRows($q_auth);  
             
           if($my_auth != ''){       
                  
                $id = $my_auth['id_user'];  
                $this->identified = 'yes';  
                  
                //once the user has been authenticated, we go fetch its private language settings  
                $user_lang = "SELECT id_user_settings, user_settings_id_user, user_settings_language   
                        FROM user_settings   
                        WHERE user_settings_id_user = '".$id."'  
                        ";  
                  
                MySQLModel::get_mysql_instance()->executeQuery($user_lang);  
                  
                $userlang = MySQLModel::get_mysql_instance()->getRows($user_lang);  
   
                if($userlang != ''){$this->user_lang = $userlang['user_settings_language'];}  
   
                //and write the user to a session                 
                $this->stay_in($id, $user, $pwd, $this->user_lang);  
                  
                // IF LAST URL REMEMBERED  
                if(isset($_SESSION['last_url']) && $_SESSION['last_url'] != ''){  
                     $exp_url = explode("&",$_SESSION['last_url']);  
                     $xpr = LN.'=';  
                     $parts = '';  
                     $rep = LN.'='.CURRENT_LANG.'&';  
                       
                     foreach($exp_url as $xpurl){       
                          if(preg_match('/'.$xpr.'/',$xpurl)){  
                               if($xpurl_new = preg_replace('/'.$xpr.'/',$rep,$xpurl)){$parts .= $rep;}  
                          }  
                     else{$parts .= $xpurl.'&';}  
                     }  
                       
                     if(preg_match('/signin/',$_SESSION['last_url']) || preg_match('/fpwd/',$_SESSION['last_url'])){  
                          if(CLEAN_URLS == true){$location = CLEAN_PATH.'/'.DEFAULT_RLINK.$default_plink.'/'.CURRENT_LANG;}  
                          else{$location = $_SERVER['PHP_SELF'].'?'.RLINK.'='.DEFAULT_RLINK.$default_plink.'&'.LN.'='.CURRENT_LANG;}                           
                     }  
                     else{$location = rtrim($parts,"&");}  
                }  
                else{  
                     if(CLEAN_URLS == true){$location = CLEAN_PATH.'/'.DEFAULT_RLINK.'/'.$default_plink.CURRENT_LANG;}  
                     else{$location = $_SERVER['PHP_SELF'].'?'.RLINK.'='.DEFAULT_RLINK.$default_plink.'&'.LN.'='.CURRENT_LANG;}  
                }  
                header('Location: '.$location);exit();  
           }  
           else{$this->identified = 'wrong_credentials';}  
             
           return $this->identified;  
      }  
 ?>  
If you have survived these series up until now, you should not need an explanation for this last snippet, but just in case though… : once we have identified the user we go fetch its ID and language than determine the location to which we will redirect that user, based on whether a last URL was recorded and whether clean URLS are enabled or not.

Finally, now that our user has been able to log in manually, that same user may be wanting to review and update its personal data. This is just what profile module is all about. The profile module, which is alike the settings one, only differs from let’s say the signin module in that we need to get the user’s data in order to present it in the profile form.
 <?php  
      /* from application/controllers/ProfileController.php */  
   
      ...  
      // while in properties  
      private $user_data;  
      ...  
   
      ...  
      //while inside the initialize() method, after the intercept_data() method, and before assign_theme()  
      $this->registry->template->user_data = $this->getProfileFormData(); //we call the method right bellow  
      ...  
        
      private function getProfileFormData(){  
           $model = ProfileModel::getInstance();  
           return $model->getProfileFormData(); //we call and returns the method we need  
      }  
      ...  
 ?>  
and so, in the profile model we have
 <?php  
   
      /* from application/controllers/ProfileModel.php */  
   
      public function getProfileFormData(){  
             
           $this->user_profile=array();  
        
           //data for the profile module resides in the users table       
           $my_query_profile = "SELECT * FROM users WHERE id_user = '".$_SESSION['c_id']."' AND user_login = '".$_SESSION['c_login']."'";  
             
           MySQLModel::get_mysql_instance()->executeQuery($my_query_profile);  
             
                $this->up = MySQLModel::get_mysql_instance()->getRows($my_query_profile);  
                  
                if($this->up != ''){  
                     array_push($this->user_profile, $this->up['user_login'],$this->up['user_email'],$this->up['user_country'],$this->up['user_birthdate']);  
                }  
                  
                return $this->user_profile;  
      }  
 ?>  
It also differs in that once we have intercepted our form data, we need to update the database with it, and here is how we will do it
 <?php  
   
      /* from application/controllers/ProfileModel.php */  
   
      public function get_form_feedback($parms){  
             
           if(!empty($parms)){  
             
                $error_list = array();  
   
                $error_check_profile = self::check_profile_form_errors($parms);  
   
                if (sizeof($error_check_profile) > 0){array_push($error_list,$error_check_profile);}  
             
                if(sizeof($error_list) > 0){  
                     $this->form_feedback = $error_list;            
                }  
                else{            
                     $field_list = array('login','password','email','country','birthdate');  
                  
                          foreach($field_list as $fl){  
                               if(in_array($fl,$parms) && isset($_POST[$fl])){$$fl = $_POST[$fl];}  
                          }  
                  
                     $now = date("Y-m-d H:i:s");  
   
                     $q_update_profile = "UPDATE users   
                          SET user_email = '".$email."',  
                          user_country = '".$country."',  
                          user_birthdate = '".$birthdate."',  
                          user_timestamp = '".$now."'  
                          WHERE user_login = '".$login."'  
                          AND user_password = md5('".$password."')  
                          AND user_timestamp != '".$now."'  
                     ";                 
   
                     if(MySQLModel::get_mysql_instance()->executeQuery($q_update_profile) == 0){  
                          if(MySQLModel::get_mysql_instance()->affectedRows() == 1) {$this->form_feedback = 'success';}  
                          else{echo MySQLModel::get_mysql_instance()->mysql_fetch_errors();$this->form_feedback = 'wrong_password';}  
                     }  
                     else{$this->form_feedback = 'failed';}  
                }  
             
           }            
                return $this->form_feedback;  
      }  
 ?>  
You can see it’s very similar to any other get_form_feedback() methods from other models, only here we update the data. Note the use of the field user_timestamp which exists only to force MySQL to affect something if one of the values has then changed. Otherwise, if the update is performed and no rows were affected, there’s no programatic way to know it worked and so our code could only return a failed state, or nothing at best.

Regarding the profile form in profile.php, its structure appears to be no different than the registration form. You will only have to make sure your HTML form field names correspond to what is being processed in the model. On the model side, the check_profile_form_errors() methods is similar to check_reg_form_errors() so there’s no point repeating the same code over and over and besides, this will be a great exercise for you to test the knowledge you have acquired so far from these series.

By the way, let’s take a look at the user_settings table, because you will need it too
 --  
 -- Table structure for table `user_settings`  
 --  
   
 CREATE TABLE `user_settings` (  
  `id_user_settings` int(11) NOT NULL,  
  `user_settings_id_user` int(11) NOT NULL,  
  `user_settings_language` enum('de','es','fr','nl','ru','se','uk','en') NOT NULL DEFAULT 'en',  
  `user_settings_nipp` enum('10','15','20','50','75','100','150') NOT NULL DEFAULT '15',  
  `user_settings_newsletter` varchar(3) NOT NULL,  
  `user_settings_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;  
   
 --  
 -- Dumping data for table `user_settings`  
 --  
   
 INSERT INTO `user_settings` (`id_user_settings`, `user_settings_id_user`, `user_settings_language`, `user_settings_nipp`, `user_settings_newsletter`, `user_settings_timestamp`) VALUES  
 (1, 1, 'fr', '20', '', '2015-11-19 08:05:46');  
   
 --  
 -- Indexes for dumped tables  
 --  
   
 --  
 -- Indexes for table `user_settings`  
 --  
 ALTER TABLE `user_settings`  
  ADD PRIMARY KEY (`id_user_settings`);  
   
 --  
 -- AUTO_INCREMENT for dumped tables  
 --  
   
 --  
 -- AUTO_INCREMENT for table `user_settings`  
 --  
 ALTER TABLE `user_settings`  
  MODIFY `id_user_settings` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;  
Note the user_settings_nipp and newsletter fields are only here for demonstration, so you may remove them but as long as you modify your settings model accordingly of course.

Finally and regarding the forgotten password module or the change password module, there is nothing magical about them, the fpwd module is somehow very identical to the signin module and the pwdc module is relatively similar to the signin module too. Knowing that should get you on track faster while you attempt at building those new modules. Note that regarding the fpwd module, an email is sent with a new password, you may use the generate_new_pwd() method from UtilsModel.php.

Now we could not be coming full circle if we did not get in the subject of authentication again. Why? Simply because users should not be allowed to enter their profile data unless they are already logged in, which makes sense. So that, the first call inside the initialize() method of your profile controller will be to the get_module_auth() method, as described bellow
 <?php  
      /* from application/controllers/ProfileController/php */  
        
      public function initialize(){  
   
           $utils = new UtilsModel();            
           $this->registry->template->module_auth = $utils->get_module_auth();  
           ...  
      }       
 ?>  
Now the function get_module_auth() method itself, pretty much and explicit one.
 <?php  
   
      /* from application/models/UtilsModel/php */  
   
      public function get_module_auth(){  
        
           if(CURRENT_PLINK){  
                if(CLEAN_URLS == true){$pg = '/'.CURRENT_PLINK;} else{$pg = '&'.PLINK.'='.CURRENT_PLINK;}  
           }  
           else{$pg = '';}       
                       
           if(isset($_SESSION['c_login']) && isset($_SESSION['c_pwd']) && $_SESSION['c_login']!='' && $_SESSION['c_pwd']!=''){  
                $this->module_auth = 0;  
           }  
           else{  
                $this->module_auth = 1;  
                if(CLEAN_URLS == true){$location = CLEAN_PATH.'/signin/'.CURRENT_LANG;}  
                else{$location = $_SERVER['PHP_SELF'].'?'.RLINK.'=signin&'.LN.'='.CURRENT_LANG;}  
                header('Location: '.$location);exit();  
           }  
      }  
 ?>  
And that wraps up this fifth settlement dedicated to building a user interface that allows someone to register, sign in, review and update profile. From this, you can now easily extend what you have already learned to build a settings module, same as a profile module but that will rely on the user_settings table, a fpwd module that will handle cases of forgotten passwords and even a pwdc module, that will allow users to change password, first checking of course on the validity of the password they enter, then checking that the new and confirm new passwords are of correct format and match each other too.

Next time I will get into full detail as to the subject of theme and language handling, reviewing all aspects of what makes the visual identity of this project and also discuss a method for localizing strings.

This article first appeared Thursday the 7th of January 2016 on RolandC.net.