Google Chrome actually stealing mundane ideas from Opera?

Note from the future: this was originally written before Opera became Chrome Jr.

A week or so ago, not sure when, Opera updated; nothing was really different, except the autosuggest / autofill items were now bold. I didn't think much of it, until Chrome updated, suddenly theirs were bold too.

What the…?

So, I went out looking for answers. One of the first things I noticed was since about May 4th, a lot of people were talking about Chrome's bold autofill lists; primarily asking how they could restyle that. I didn't find anyone talking about how Opera had done the same thing.

Granted, Opera is not really the most popular browser, and I use it for testing purposes primarily, but nevertheless this change really stands out to me. Not necessarily because one browser copied another, but because the copy is so mundane and silly. Is there some massive benefit to bold auto-fill or bold auto-suggest items?

Is it so revolutionary that when Opera updated, some Google Chrome hacker said "HOLY TOLEDO, WE GOTTA DO THAT, RIGHT NOW, PUSH IT OUT ASAP! WE CAN'T BE LEFT BEHIND! THIS IS DISRUPTIVE!"?

I've tried to find out if there's any discussion from either company regarding this, but I sure can't find it. If anyone has any information, I'd sure like to know why this was done, because it's so goofy.

Exchange and PHP, a nightmare with a solution

OK that title is not very smooth or clever, but a project I worked on several years ago was syncing Microsoft Exchange with PHP. There's a lot out there not documented, primarily because there are several problems with PHP's implementation of XML and Microsoft's over-use of namespaces and so forth. However, with some serious hacking, I've come up with some solutions.

This was initially based on some code I had found online, however it was so long ago I have not been able to figure it out so I cannot provide proper attribution. Additionally, impersonation does work, and I've seen a lot of people have a problem with this as well.

The biggest thing is it requires a lot of manual XML to actually work, you can't build the objects and expect it to work correctly, it literally will not, primarily due to PHP's SOAP class.

The primary benefit of this code is to see how I hacked around dealing with PHP's issues when it came to trying to deal with the Exchange calendar and task system.

Feel free to reply with any fixes, updates, etc.

A few things to note:

  • When I created this, it was a hackfest with very little time, so the code is not my best work at all; I mean it uses globals for god sake, and improper OOP, improper usage of PSR, classes doing far too much, etc.
  • This code was built for PHP 5.2 – 5.4, and takes no advantage of PHP 7 or anything.
  • Yes, those two above mean I am embarrassed by the quality of this code, but hiding it isn't really useful to anyone, especially because I am not using it in my project any more.
  • This will not work out of box, because it was built specifically for my project, however because it was such a nightmare and other people are struggling, I'm sharing it. It should give you a good idea of where to start and how to deal with some hiccoughs.
  • I assume you've already got the basis down, and you've got messages.xsd, Services.wsdl, and types.xsd, and all the other stuff. Basically I am just assuming you've got it working, you're just running into problems actually doing anything useful.

Usage from my job which pull down basically everything first. This is because repeating calendar entries are only listed once, so you have to pre-grab and store everything in order to deal with it later. The most important aspects are storing the ID and change key so that you can properly manage it later. If someone edits a calendar entry, the change key will be "changed," so every once in a while, depending on your desire of accuracy, you may need to resync everything, unless you find a better way to do it (if so please share).

   $Exchange = new Exchange('phpuser@mydomain.com', 'mypassword');

   $entries = $Exchange->calendarList(600000, 0, True, 'Beginning');

Here's the classes:

<?php
/****************************************
 * @author  @tonyshowoff
 * @version 1
 * @license MIT/X11
 ****************************************/

$GLOBALS['_EXCHANGE'] = array(
   'impersonate' => Null, /* Email address of account to impersonate */
   'curl'        => Null,
   'full_debug'  => False, /* This will display all traffic as well */
   'curl_debug'  => False /* Set cURL as verbose response */
);

$GLOBALS['_CONFIG'] = array(
   'debug_mode' => True,
   'exchange_cache' => '', /* Pathh which contains your Services.wsdl, etc */
);

class Exchange {
   public $lastError;
   private $client;
   private $impersonate;
   private $pass;
   private $user;
   private $wsdl;

   function __construct($user, $pass, $impersonate = Null) {
      $this->wsdl                          = $GLOBALS['_CONFIG']['exchange_cache'] . '/Services.wsdl';
      $this->user                          = $user;
      $this->pass                          = $pass;
      $this->impersonate                   = $impersonate;
      $GLOBALS['_EXCHANGE']['impersonate'] = $impersonate;
      $GLOBALS['_EXCHANGE']['curl']        = Null;
      $this->client                        = new ExchangeNTLMSoapClient($this->wsdl);
      $this->client->user                  = $this->user;
      $this->client->password              = $this->pass;
   }

   function calendarEntry($id) {
      $this->setup();

      $req_xml                       = new stdClass();
      $req_xml->ItemShape            = new stdClass();
      $req_xml->ItemShape->BaseShape = 'AllProperties';
      $req_xml->ItemIds              = new stdClass();
      $req_xml->ItemIds->ItemId      = new stdClass();
      $req_xml->ItemIds->ItemId->Id  = $id;

      try {
         $response = $this->client->GetItem($req_xml);
      } catch (Exception $e) {
         echo 'calendarEntry() exception on ID "' . $id . '": ', $e->getMessage(), "\n";
         $this->teardown();

         return False;
      }

      $this->teardown();

      if ($response->ResponseMessages->GetItemResponseMessage->ResponseCode == 'NoError') {
         return $response->ResponseMessages->GetItemResponseMessage->Items->CalendarItem;
      } else {
         return False;
      }
   }

   function calendarEventAdd($subject, $start, $end, $location, $body = '', $type = 'HTML', $isallday = False) {
      /*
        $start and $end must be in ISO date format or rather 'c' in PHP date format
        This function returns an array of item details upon success and False upon failure. */

      $this->setup();

      $event_xml                                               = new stdClass();
      $event_xml->SendMeetingInvitations                       = 'SendToNone';
      $event_xml->SavedItemFolderId                            = new stdClass();
      $event_xml->SavedItemFolderId->DistinguishedFolderId     = new stdClass();
      $event_xml->SavedItemFolderId->DistinguishedFolderId->Id = 'calendar';
      $event_xml->Items                                        = new stdClass();
      $event_xml->Items->CalendarItem                          = new stdClass();
      $event_xml->Items->CalendarItem->Subject                 = $subject;
      $event_xml->Items->CalendarItem->Start                   = $start;
      $event_xml->Items->CalendarItem->End                     = $end;
      $event_xml->Items->CalendarItem->Body                    = new stdClass();
      $event_xml->Items->CalendarItem->Body->BodyType          = $type;
      $event_xml->Items->CalendarItem->Body->_                 = $body;
      $event_xml->Items->CalendarItem->IsAllDayEvent           = $isallday;
      $event_xml->Items->CalendarItem->LegacyFreeBusyStatus    = 'Busy'; // Legacy only (never use)
      $event_xml->Items->CalendarItem->Location                = $location;

      try {
         $response = $this->client->CreateItem($event_xml);
      } catch (Exception $e) {
         echo 'calendarEventAdd() exception: ', $e->getMessage(), "\n";
         $this->teardown();

         return False;
      }

      $this->teardown();

      if ($response->ResponseMessages->CreateItemResponseMessage->ResponseCode == 'NoError') {
         return ['ItemId'    => $response->ResponseMessages->CreateItemResponseMessage->Items->CalendarItem->ItemId->Id,
                 'ChangeKey' => $response->ResponseMessages->CreateItemResponseMessage->Items->CalendarItem->ItemId->ChangeKey];
      } else {
         $this->lastError = $response->ResponseMessages->CreateItemResponseMessage->ResponseCode;
         log_out_error('exchange', "Calendar:\r\nError: " . print_r($this->lastError, 1) . "\r\n\r\nInitial data:\r\n" . print_r($event_xml, 1));

         return False;
      }
   }

   /**
    * Sets up strream handling. Internally used.
    *
    * @access private
    * @return void
    */
   private function setup() {
      stream_wrapper_unregister('http');
      stream_wrapper_register('http', 'ExchangeNTLMStream') or die('Failed to register protocol: 8c251e34-77e6-41ce-88da-4dbc90bc8a60');
   }

   /**
    * Tears down stream handling. Internally used.
    *
    * @access private
    * @return void
    */

   function teardown() {
      stream_wrapper_restore('http');
   }

   function calendarEventDelete($item_id, $type = 'HardDelete') {
      $this->setup();

      $DeleteItem                           = new stdClass();
      $DeleteItem->DeleteType               = $type;
      $DeleteItem->SendMeetingCancellations = 'SendToNone';
      $DeleteItem->ItemIds                  = new stdClass();
      $DeleteItem->ItemIds->ItemId          = new stdClass();
      $DeleteItem->ItemIds->ItemId->Id      = $item_id;

      try {
         $response = $this->client->DeleteItem($DeleteItem);
      } catch (Exception $e) {
         echo 'calendarEventDelete() exception on ID "' . $item_id . '": ', $e->getMessage(), "\n";
         $this->teardown();

         return False;
      }

      $this->teardown();

      if ($response->ResponseMessages->DeleteItemResponseMessage->ResponseCode == 'NoError') {
         return True;
      } else {
         $this->lastError = $response->ResponseMessages->DeleteItemResponseMessage->ResponseCode;
         log_out_error('exchange', "Calendar:\r\nError: " . print_r($this->lastError, 1) . "\r\n\r\nInitial data:\r\n" . print_r($DeleteItem, 1));

         return False;
      }
   }

   function calendarList($limit = 10, $offset = 0, $full = False, $point = 'Beginning') {
      $this->setup();

      $FindItem            = new stdClass();
      $FindItem->Traversal = 'Shallow';
      $FindItem->ItemShape = new stdClass();

      if (True == $full) {
         $FindItem->ItemShape->BaseShape = 'AllProperties';
      } else {
         $FindItem->ItemShape->BaseShape            = 'IdOnly';
         $FindItem->ItemShape->AdditionalProperties = new SoapVar(
            '<ns1:AdditionalProperties>
                                <ns1:FieldURI FieldURI="calendar:CalendarItemType"/>
                                <ns1:FieldURI FieldURI="calendar:IsMeeting"/>
                                <ns1:FieldURI FieldURI="calendar:IsRecurring"/>
                                <ns1:FieldURI FieldURI="calendar:Organizer"/>
                                <ns1:FieldURI FieldURI="calendar:Start"/>
                                <ns1:FieldURI FieldURI="calendar:End"/>
                                <ns1:FieldURI FieldURI="calendar:Duration"/>
                                <ns1:FieldURI FieldURI="calendar:IsAllDayEvent"/>
                                <ns1:FieldURI FieldURI="calendar:Location"/>
                                <ns1:FieldURI FieldURI="calendar:MeetingRequestWasSent"/>
                                <ns1:FieldURI FieldURI="calendar:MyResponseType"/>
                                <ns1:FieldURI FieldURI="calendar:AppointmentSequenceNumber"/>
                                <ns1:FieldURI FieldURI="calendar:AppointmentState"/>
                                <ns1:FieldURI FieldURI="item:Categories"/>
                                <ns1:FieldURI FieldURI="item:HasAttachments"/>
                                <ns1:FieldURI FieldURI="item:ReminderDueBy"/>
                                <ns1:FieldURI FieldURI="item:ReminderIsSet"/>
                                <ns1:FieldURI FieldURI="item:ReminderMinutesBeforeStart"/>
                                <ns1:FieldURI FieldURI="item:Subject"/>
                                <ns1:FieldURI FieldURI="item:ItemClass"/>
                                <ns1:FieldURI FieldURI="item:Sensitivity"/>
                                <ns1:FieldURI FieldURI="item:DateTimeReceived"/>
                                <ns1:FieldURI FieldURI="item:Importance"/>
                                <ns1:FieldURI FieldURI="item:IsDraft"/>
                                <ns1:FieldURI FieldURI="item:DateTimeCreated"/>
                        </ns1:AdditionalProperties>', XSD_ANYXML);
      }

      $FindItem->IndexedPageItemView                     = new stdClass();
      $FindItem->IndexedPageItemView->MaxEntriesReturned = $limit;
      $FindItem->IndexedPageItemView->Offset             = $offset;
      $FindItem->IndexedPageItemView->BasePoint          = $point;

      $FindItem->ParentFolderIds                            = new stdClass();
      $FindItem->ParentFolderIds->DistinguishedFolderId     = new stdClass();
      $FindItem->ParentFolderIds->DistinguishedFolderId->Id = 'calendar';

      try {
         $response = $this->client->FindItem($FindItem);
      } catch (Exception $e) {
         return False;
      }

      if ($response->ResponseMessages->FindItemResponseMessage->ResponseCode != 'NoError') {
         $this->lastError = $response->ResponseMessages->FindItemResponseMessage->ResponseCode;

         return false;
      }

      if (empty($response->ResponseMessages->FindItemResponseMessage->RootFolder->Items->CalendarItem)) {
         $items = False;
      } else {
         $items = $response->ResponseMessages->FindItemResponseMessage->RootFolder->Items->CalendarItem;

         if (!is_array($items)) {
            $items = $response->ResponseMessages->FindItemResponseMessage->RootFolder->Items;
         }
      }

      $this->teardown();

      return $items;
   }

   function calendar_event_update($item_id, $changekey, $subject, $start, $end, $location, $body = '', $type = 'HTML', $isallday = False) {
      /*
        $start and $end must be in ISO date format or rather 'c' in PHP date format
        This function returns an array of item details upon success and False upon failure. */

      $this->setup();

      $update_xml = new stdClass();

      $update_xml->SendMeetingInvitationsOrCancellations = 'SendToNone';
      $update_xml->MessageDisposition                    = 'SaveOnly';
      $update_xml->ConflictResolution                    = 'AlwaysOverwrite';

      $update_xml->ItemChanges                     = new stdClass();
      $update_xml->ItemChanges->ItemChange         = new stdClass();
      $update_xml->ItemChanges->ItemChange->ItemId = new stdClass();

      $update_xml->ItemChanges->ItemChange->ItemId->Id = $item_id;

      $update_xml->ItemChanges->ItemChange->ItemId->ChangeKey = $changekey;

      $update_xml->ItemChanges->ItemChange->Updates = new stdClass();

      $update_xml->ItemChanges->ItemChange->Updates = new SoapVar(
         '<ns1:Updates>
                                <ns1:SetItemField>
                                        <ns1:FieldURI FieldURI="item:Subject" />
                                        <ns1:CalendarItem>
                                                <ns1:Subject>' . htmlspecialchars($subject) . '</ns1:Subject>
                                        </ns1:CalendarItem>
                                </ns1:SetItemField>
                                <ns1:SetItemField>
                                        <ns1:FieldURI FieldURI="calendar:Location" />
                                        <ns1:CalendarItem>
                                                <ns1:Location>' . htmlspecialchars($location) . '</ns1:Location>
                                        </ns1:CalendarItem>
                                </ns1:SetItemField>
                                <ns1:SetItemField>
                                        <ns1:FieldURI FieldURI="calendar:Start" />
                                        <ns1:CalendarItem>
                                                <ns1:Start>' . $start . '</ns1:Start>
                                        </ns1:CalendarItem>
                                </ns1:SetItemField>
                                <ns1:SetItemField>
                                        <ns1:FieldURI FieldURI="calendar:End" />
                                        <ns1:CalendarItem>
                                                <ns1:End>' . $end . '</ns1:End>
                                        </ns1:CalendarItem>
                                </ns1:SetItemField>
                                <ns1:SetItemField>
                                        <ns1:FieldURI FieldURI="item:Body" />
                                        <ns1:CalendarItem>
                                                <ns1:Body BodyType="HTML">' . htmlspecialchars($body) . '</ns1:Body>
                                        </ns1:CalendarItem>
                                </ns1:SetItemField>
                        </ns1:Updates>', XSD_ANYXML);

      try {
         $response = $this->client->UpdateItem($update_xml);
      } catch (Exception $e) {
         echo 'calendar_event_update() exception on ID "' . $item_id . '": ', $e->getMessage(), "\n";
         $this->teardown();

         return False;
      }

      $this->teardown();

      if ($response->ResponseMessages->UpdateItemResponseMessage->ResponseCode == 'NoError') {
         return ['changekey' => $response->ResponseMessages->UpdateItemResponseMessage->Items->CalendarItem->ItemId->ChangeKey];
      } else {
         $this->lastError = $response->ResponseMessages->UpdateItemResponseMessage->ResponseCode;
         log_out_error('exchange', "Calendar:\r\nError: " . print_r($this->lastError, 1) . "\r\n\r\nInitial data:\r\n" . print_r($update_xml, 1));

         return False;
      }
   }

   function close() {
      curl_close($GLOBALS['_EXCHANGE']['curl']);

      $GLOBALS['_EXCHANGE']['impersonate'] = Null;
      $GLOBALS['_EXCHANGE']['curl']        = Null;

      if ($GLOBALS['_CONFIG']['debug_mode'] == True) {
         echo " [D] Close Exchange connection.\n";
      }
   }

   /**
    * Deletes a message in the mailbox of the current user.
    *
    * @access public
    *
    * @param ItemId $ItemId     (such as one returned by get_messages)
    * @param string $deletetype . (default: "HardDelete")
    *
    * @return bool $success (true: message was deleted, false: message failed to delete)
    */
   function delete_message($ItemId, $deletetype = "HardDelete") {
      $this->setup();

      $DeleteItem->DeleteType      = $deletetype;
      $DeleteItem->ItemIds->ItemId = $ItemId;

      $response = $this->client->DeleteItem($DeleteItem);

      $this->teardown();

      if ($response->ResponseMessages->DeleteItemResponseMessage->ResponseCode == "NoError") {
         return true;
      } else {
         $this->lastError = $response->ResponseMessages->DeleteItemResponseMessage->ResponseCode;

         return false;
      }
   }

   function getAttachment($item_id, $attachment_id) {
      // Doesn't seem to work.
      $this->setup();

      $req_xml                                = new stdClass();
      $req_xml->ItemShape                     = new stdClass();
      $req_xml->ItemShape->BaseShape          = 'AllProperties';
      $req_xml->ItemIds                       = new stdClass();
      $req_xml->ItemIds->ItemId               = new stdClass();
      $req_xml->ItemIds->ItemId->Id           = $item_id;
      $req_xml->ItemIds->ItemId->AttachmentId = $attachment_id;

      try {
         $response = $this->client->GetAttachment($req_xml);
      } catch (Exception $e) {
         echo 'getAttachment() exception on ID "' . $item_id . '": ', $e->getMessage(), "\n";
         $this->teardown();

         return False;
      }

      $this->teardown();

      if ($response->ResponseMessages->GetItemResponseMessage->ResponseCode == 'NoError') {
         return $response->ResponseMessages->GetItemResponseMessage->Items->Attachment;
      } else {
         return False;
      }
   }

   function getInbox($limit = 10, $offset = 0) {
      $this->setup();

      $FindItem            = new stdClass();
      $FindItem->Traversal = 'Shallow';
      $FindItem->ItemShape = new stdClass();

      $FindItem->ItemShape->BaseShape = 'IdOnly';

      $FindItem->ItemShape->AdditionalProperties = new SoapVar(
         '<ns1:AdditionalProperties>
                                <ns1:FieldURI FieldURI="item:Subject"/>
                        </ns1:AdditionalProperties>', XSD_ANYXML);

      $FindItem->IndexedPageItemView                     = new stdClass();
      $FindItem->IndexedPageItemView->MaxEntriesReturned = $limit;
      $FindItem->IndexedPageItemView->Offset             = $offset;
      $FindItem->IndexedPageItemView->BasePoint          = 'Beginning';

      $FindItem->ParentFolderIds                            = new stdClass();
      $FindItem->ParentFolderIds->DistinguishedFolderId     = new stdClass();
      $FindItem->ParentFolderIds->DistinguishedFolderId->Id = 'inbox';

      try {
         $response = $this->client->FindItem($FindItem);
      } catch (Exception $e) {
         return False;
      }

      if ($response->ResponseMessages->FindItemResponseMessage->ResponseCode != 'NoError') {
         $this->lastError = $response->ResponseMessages->FindItemResponseMessage->ResponseCode;

         return false;
      }

      $items = $response->ResponseMessages->FindItemResponseMessage->RootFolder->Items->Message;

      $this->teardown();

      return $items;
   }

   function itemExists($id) {
      $this->setup();

      $req_xml                       = new stdClass();
      $req_xml->ItemShape            = new stdClass();
      $req_xml->ItemShape->BaseShape = 'IdOnly';
      $req_xml->ItemIds              = new stdClass();
      $req_xml->ItemIds->ItemId      = new stdClass();
      $req_xml->ItemIds->ItemId->Id  = $id;

      try {
         $response = $this->client->GetItem($req_xml);
      } catch (Exception $e) {
         echo 'itemExists() exception on ID "' . $id . '": ', $e->getMessage(), "\n";

         return False;
      }

      $this->teardown();

      if ($response->ResponseMessages->GetItemResponseMessage->ResponseCode == 'NoError') {
         return True;
      } else {
         return False;
      }
   }

   function sendMessage($to, $subject, $content, $type = 'HTML', $save = False, $read = True) {
      /*
        This sends a message through Exchange as the user that's logged in via init.

        $to         - The email address we're sending to, can be internal or external
        $subject        - The subject
        $content        - The content, style dependent on $type
        $type         - Default is 'Text', but 'HTML' should be used for HTML emails
        $save         - Default F, T/F save to user's sent folder?
        $read         - Default F, T/F mark as read. @BUG: Doesn't seem to work, possibly missing another field?
       */

      $this->setup();

      $msg_xml = new stdClass();

      if ($save == True) {
         $msg_xml->MessageDisposition                           = 'SendOnly';
         $msg_xml->SavedItemFolderId                            = new stdClass();
         $msg_xml->SavedItemFolderId->DistinguishedFolderId     = new stdClass();
         $msg_xml->SavedItemFolderId->DistinguishedFolderId->Id = 'sentitems';
      } else {
         $msg_xml->MessageDisposition = 'SendOnly';
      }

      $msg_xml->Items                                               = new stdClass();
      $msg_xml->Items->Message                                      = new stdClass();
      $msg_xml->Items->Message->ItemClass                           = 'IPM.Note';
      $msg_xml->Items->Message->Subject                             = $subject;
      $msg_xml->Items->Message->Body                                = new stdClass();
      $msg_xml->Items->Message->Body->BodyType                      = $type;
      $msg_xml->Items->Message->Body->_                             = $content;
      $msg_xml->Items->Message->ToRecipients                        = new stdClass();
      $msg_xml->Items->Message->ToRecipients->Mailbox               = new stdClass();
      $msg_xml->Items->Message->ToRecipients->Mailbox->EmailAddress = $to;

      if ($read == True) {
         $msg_xml->Items->Message->IsRead = 'true';
      }

      try {
         $response = $this->client->CreateItem($msg_xml);
      } catch (Exception $e) {
         return False;
      }

      $this->teardown();

      if ($response->ResponseMessages->CreateItemResponseMessage->ResponseCode == 'NoError') {
         return True;
      } else {
         $this->lastError = $response->ResponseMessages->CreateItemResponseMessage->ResponseCode;
         log_out_error('exchange', "sendMessage:\r\nError: " . print_r($this->lastError, 1) . "\r\n\r\nInitial data:\r\n" . print_r($msg_xml, 1));

         return False;
      }
   }
}

class ExchangeNTLMSoapClient extends NTLMSoapClient {
   public $password = '';
   public $user = '';
}

class ExchangeNTLMStream extends NTLMStream {
   //protected $user = '';
   //protected $password = '';
}

class ImpersonationHeader {
   var $ConnectingSID;

   /*ExchangeImpersonationType and the
   ConnectingSIDType*/
   function __construct($email) {
      $this->ConnectingSID                = new stdClass();
      $this->ConnectingSID->PrincipalName = $email;
      /*
            $this->ConnectingSID = new stdClass();
            $this->ConnectingSID->PrimarySmtpAddress = $email;
      */
   }
}

class NTLMSoapClient extends SoapClient {
   function __doRequest($request, $location, $action, $version, $one_way = 0) {

      if ($GLOBALS['_EXCHANGE']['full_debug'] == True) {
         echo 'Request: ' . $request . "\n<br />";
         echo 'Location: ' . $location . "\n<br />";
         echo 'Action: ' . $action . "\n<br />";
         echo 'Version: ' . $version . "\n<br />";
         echo 'One-way: ' . $one_way . "\n<br />";
         echo 'Request: ' . print_r($request, 1);
      }

      $headers = [
         'Method: POST',
         'Connection: Keep-Alive',
         'User-Agent: PHP-SOAP-CURL',
         'Content-Type: text/xml; charset=utf-8',
         'SOAPAction: "' . $action . '"',
      ];

      // @TODO: Somehow we need to pass the $impersonate var
      //        from the exchange class to here, probably just in init
      //        or something, but we'll do this hack for now.

      if ($GLOBALS['_EXCHANGE']['impersonate'] != Null) {
         // @TODO / @HACK: Ideally we'd want to just use the SOAP system
         // built into PHP to do this, but it has a flaw which
         // will not let you properly impersonate someone.
         // Until it's fixed (been 2 years as of now) we do this.

         $xml = '<SOAP-ENV:Header>';
         $xml .= '<ns1:ExchangeImpersonation>';
         $xml .= '<ns1:ConnectingSID>';
         $xml .= '<ns1:PrimarySmtpAddress>' . $GLOBALS['_EXCHANGE']['impersonate'] . '</ns1:PrimarySmtpAddress>';
         $xml .= '</ns1:ConnectingSID>';
         $xml .= '</ns1:ExchangeImpersonation>';
         $xml .= '</SOAP-ENV:Header>';

         $request = str_replace('><SOAP-ENV:Body', '>' . $xml . '<SOAP-ENV:Body', $request);
      }

      $this->__last_request_headers = $headers;
      $GLOBALS['_EXCHANGE']['curl'] = curl_init($location);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_RETURNTRANSFER, true);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_HTTPHEADER, $headers);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_POST, true);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_POSTFIELDS, $request);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_USERPWD, $this->user . ':' . $this->password);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_SSL_VERIFYPEER, false);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_SSL_VERIFYHOST, false);
      curl_setopt($GLOBALS['_EXCHANGE']['curl'], CURLOPT_VERBOSE, $GLOBALS['_EXCHANGE']['curl_debug']);
      $response = curl_exec($GLOBALS['_EXCHANGE']['curl']);

      if ($GLOBALS['_EXCHANGE']['full_debug'] == True) {
         echo 'Response: ' . print_r($response, 1);
      }

      // @TODO: If there's an error we need to log it, but
      //        this may need to be done at all the functions
      //        which parse this, rather than here. Though we
      //        should still log all CURL errors.

      return $response;
   }

   function __getLastRequestHeaders() {
      return implode("n", $this->__last_request_headers) . "n";
   }
}

class NTLMStream {
   private $buffer;
   private $mode;
   private $opened_path;
   private $options;
   private $path;
   private $pos;

   public function stream_close() {
      echo "[NTLMStream::stream_close] n";
      curl_close($this->ch);
   }

   public function stream_eof() {
      echo "[NTLMStream::stream_eof] ";
      if ($this->pos > strlen($this->buffer)) {
         echo "true n";

         return true;
      }
      echo "false n";

      return false;
   }

   public function stream_flush() {
      echo "[NTLMStream::stream_flush] n";
      $this->buffer = null;
      $this->pos    = null;
   }

   public function stream_open($path, $mode, $options, $opened_path) {
      echo "[NTLMStream::stream_open] $path , mode=$mode n";
      $this->path        = $path;
      $this->mode        = $mode;
      $this->options     = $options;
      $this->opened_path = $opened_path;
      $this->createBuffer($path);

      return true;
   }

   private function createBuffer($path) {
      if ($this->buffer) {
         return;
      }
      echo "[NTLMStream::createBuffer] create buffer from : $pathn";
      $this->ch = curl_init($path);
      curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($this->ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
      curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
      curl_setopt($this->ch, CURLOPT_USERPWD, $this->user . ':' . $this->password);
      echo $this->buffer = curl_exec($this->ch);
      echo "[NTLMStream::createBuffer] buffer size : " . strlen($this->buffer) . "bytesn";
      $this->pos = 0;
   }

   /* return the position of the current read pointer */

   public function stream_read($count) {
      echo "[NTLMStream::stream_read] $count n";
      if (strlen($this->buffer) == 0) {
         return false;
      }
      $read = substr($this->buffer, $this->pos, $count);
      $this->pos += $count;

      return $read;
   }

   public function stream_stat() {
      echo "[NTLMStream::stream_stat] n";
      $this->createBuffer($this->path);
      $stat = [
         'size' => strlen($this->buffer),
      ];

      return $stat;
   }

   public function stream_tell() {
      echo "[NTLMStream::stream_tell] n";

      return $this->pos;
   }

   public function stream_write($data) {
      echo "[NTLMStream::stream_write] n";
      if (strlen($this->buffer) == 0) {
         return false;
      }

      return true;
   }

   /* Create the buffer by requesting the url through cURL */

   public function url_stat($path, $flags) {
      echo "[NTLMStream::url_stat] n";
      $this->createBuffer($path);
      $stat = [
         'size' => strlen($this->buffer),
      ];

      return $stat;
   }
}

class UpdateResponse {

}

?>

Is PHP out of fashion?

One thing I keep running into is the claim that "PHP is out of fashion," which I don't quite understand considering PHP is the most popular server-side language on the entire Internet, and many of the top websites in the world use it. Indeed, even on Twitter recently I had this conversation (edited for readability, see link for context):

Faizan Javed (‏@faizanj): A bigger issue – what is it with valley startups and PHP?
Tony Showoff ‏(@TonyShowoff): What do you mean? The polarisation of it, to where either it's evil or it's the only thing used?
Faizan Javed (‏@faizanj): the intense focus and controversy over an arguably out-of-fashion language.
Tony Showoff ‏(@TonyShowoff): Java is also "out of fashion" but still used, PHP is used by more web sites than any other language. I think it's inadequacy. A lot of things go in and out of fashion, but fashion doesn't reflect usefulness. Remember the coming p2p/push/xml/etc revolutions?
Faizan Javed (‏@faizanj): one can argue Cobol and Fortran are also still useful in their domains. But hip, cool and mainstream they are not.
Tony Showoff ‏(@TonyShowoff): So is hand looming one could say, but half of all internet sites don't use COBOL and half of looms aren't hand driven.

Like underwear, PHP is becoming cleaner, as if it's been washed with Tempa-Cheer on double rinse at high heat.

I think he does bring up a good point and question though. Are COBOL and Fortran fashionable at all since they still are in use, mostly in the realm of maintenance? Well, maybe, but I don't think so. As I tried to point out as best I could on Twitter, niche use cases are not the same thing as something being ubiquitous. Just as you can still find handloomers that doesn't mean machine looming is falling out of fashion, despite the rise in custom hand made items on etsy.com

I think people often confuse what's cool with what's in fashion and what's useful or available. This is less of a big deal in pop culture trends, but in the computer world it doesn't really make much sense. Sure, PHP may not be cool, I'm not sure if it ever was, but that doesn't change the fact that to this day when you want to find a web host, almost always they have PHP hosting available and not much, if anything else. Despite Python and Ruby becoming more hip, along with Erlang and the less useful other things which are some goofy spin off of another thing, they simply aren't available everywhere or ubiquitous.

So, in a sense, asking whether or not PHP is in fashion is sort of like asking whether or not underwear is in fashion. Sure it may be cool, or sexy, not to wear it, but for the most part you'll find it everywhere you look. Is that a bad analogy for PHP? Maybe, but reflecting PHP's problems over the years, I think it's pretty apt, but like underwear, PHP is becoming cleaner, as if it's been washed with Tempa-Cheer on double rinse at high heat.

But what about the numbers (click image for source information)?

stats-w3techs

php-trend-201301-netcraft

And finally, what about as far as community help goes? After all, a programming language's success and usability these days often relies on thriving communities. Well…

tiobe-community-stats

Uncool? Maybe, but no programming language has ever been cool. Out of fashion? I don't think so.

Should you trim passwords?

A question I see asked from time to time is whether or not web sites should trim passwords on account creation and login. More often than not the typical responses show both a complete lack of understanding of what "trim" even means from people who should know better, and a lack of understanding that not every user is Dade Murphy after he just got in from grindin' some serious hand rails. Though not everyone has their head up their ass or argues from a position of complete ignorance.

So consider:

  • Trim means to remove white space from the beginning and end of a string, not the center.
  • Even if you don't email passwords or display them anywhere, users will still copy/paste them to each other and various places and often this will add a space or \n to the end of the string.

A few things are clear to me based on my experience and reading what others had to say about it:

  1. People who are against trimming passwords either don't know what trim means, or think everyone else understands and uses simple security protocols like not saving passwords in word processors
  2. People who are against trimming passwords probably have never had to deal with real users or customers, and probably are some shitty wanna be Richard Stallman with more than slightly high expectations of what users can and should be able to do
  3. Users should be allowed to have spaces within the string itself, but not on either end, in order to save your support people (and possibly yourself) a lot of headaches
  4. People who are against it assume that if there's a space it's because a user intended it, but how many people actually put spaces at the end of passwords? Basically fucking nobody.

So it's simple: users should be able to use any characters they want in a password, but the password should be trimmed at both create and login. There's also no reason to warn people that passwords are trimmed, again because nobody understands what the fuck this means, even apparently technical people. We know from massive lists of cracked and leaked plain text passwords that nobody uses spaces at the end of passwords, not even keyboard cowboys who give bad advice on Stack Overflow.

Trim away my friends, don't listen to people who probably don't even have clients.

Hamburger Icon, the illogical and unnecessary name

Stolen from the BBC
Stolen from the BBC

You may know the menu icon or navicon, sometimes called "bars", on your smart phone or tablet. When you click it or touch it, you get a menu, ta-da, pretty great.

Except lately I've seen some people, including some people I respect, call it the "hamburger" icon. This is pushed further by the media, and in fact it mostly seems to be a media thing since, well you know how reporters often run out of things to talk about…

The BBC carried this article: Hamburger icon: How these three lines mystify most people.

Are people really mystified by a standard icon seen everywhere? Somehow I doubt it.

Here's why it concerns me:

Standards are important in computing, especially when interacting with regular, "non-technical" people. Having a clear and understandable name for things makes sense. People need to connect words to actions, and if someone says "hamburger icon" instead of the more obvious "menu icon" then we've sacrificed logic to be cute.

It's always been a menu icon since it was invented, and in fact originally the cutesy name was "air vent icon," which makes more sense than hamburger, speaking of…

Here's why it makes no damn sense:

It doesn't look like a fucking hamburger! If it were a hamburger, the top and bottom lines would be wider to signify a bun, but they're all equal. It seems like a really big stretch and attempt to try to apply a name to something that does not need another god damn name.

I always thought it looked like a gripper, as one can often see on the bottom of remote controls to make sliding the battery panel off, as well as many other places. This makes sense too with smart devices, because people use their fingers on them.

Other things it looks more like: a deck of cards, a list of items, an air vent, a stack of items… Would you say the StackOverflow icon looks like someone assembling a hamburger?

Plus when I click it, no matter what, I will never get hamburgers.

Here's what we should do instead:

I don't use the term "navicon", because it seems silly to me, but so does "favicon," and that seems like a good name for technical people and development, but not regular people.

Consider the fact that people, after playing with their phones, tablets, or a web site will see it brings up a menu and they will think "oh, it's a menu icon," and it always brings up a menu wherever they see it. I don't think they'll naturally say "oh it's a hamburger, no duh, of course, as it looks nothing like a hamburger! And just like in real life, touching a hamburger brings up a menu!" even if they don't understand what it's supposed to represent, they'll just associate it with menus.

You use a menu to get a hamburger, you don't use a hamburger to get a menu, unless you're yelling at a waiter from Hamburg, Germany.

So when I say menu icon, they'll know what to click.

The best thing to do is to just not repeat this weird ass thing, it's so trivial and unnecessary.

So if it's so trivial why do I care?

Because I don't want to make interacting with users even more complicated by giving illogical, goofy names to them to be cute. We don't need to set precedent with this, otherwise what's next, calling bullets "M&Ms" because they're both round?

Want to make a list, click the M&Ms icon…

See what I mean, what's the difference? Oh yeah, bullets look more like M&Ms than the menu icon looks like a hamburger.