GithubHelp home page GithubHelp logo

flourish-classes's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flourish-classes's Issues

fHTTP - cURL/fsockopen wrapper class

Ticket #523 by steelaz:

fHTTP - flourish class to help consume HTTP based APIs. I would suggest using cURL downgradable to fsockopen. fJSON and fXML could be used to parse API response.

Comment by @Tatsh:

I am providing functionality like that in an object class here:

https://github.com/tatsh/sutra/blob/master/classes/sHTTP.php

$req = new sHTTP('http://theXMLsite.com?alt=json', 'POST');
$req->setContent('post data');
$data = fJSON::decode($req->getData());

You can use URIs with fXML as it currently is. I followed fXML's way of doing an HTTP request with stream_context_create() and file_get_contents().

fORMFile ::clearTempDir()

Ticket #585 by @craigruks:

Attached is a patch to add functionality to fORMFile that allows a user to clear out a Flourish temp directory for a file column whenever they please.

Attached file:

--- fORMFile-new.php    2011-03-08 09:49:57.000000000 -0500
+++ fORMFile.php    2011-03-08 09:49:37.000000000 -0500
@@ -9,7 +9,8 @@
  * @package    Flourish
  * @link       http://flourishlib.com/fORMFile
  * 
- * @version    1.0.0b28
+ * @version    1.0.0b29
+ * @version    1.0.0b29  Added ::clearTempDir() so that a temp dir can be cleared at any time [cr, 2011-03-08]
  * @changes    1.0.0b28  Backwards Compatibility Break - ::configureImageUploadColumn() no longer accepts the optional `$image_type` as the fourth parameter, instead ::addFImageMethodCall() must be called with `saveChanges` as the `$method` and the image type as the first parameter [wb, 2010-11-30]
  * @changes    1.0.0b27  Fixed column inheritance to properly handle non-images and inheriting into image upload columns [wb, 2010-09-18]
  * @changes    1.0.0b26  Enhanced ::configureColumnInheritance() to ensure both columns specified have been set up as file upload columns [wb, 2010-08-18]
@@ -45,6 +46,7 @@
    const addFImageMethodCall        = 'fORMFile::addFImageMethodCall';
    const addFUploadMethodCall       = 'fORMFile::addFUploadMethodCall';
    const begin                      = 'fORMFile::begin';
+   const clearTempDir               = 'fORMFile::clearTempDir';
    const commit                     = 'fORMFile::commit';
    const configureColumnInheritance = 'fORMFile::configureColumnInheritance';
    const configureFileUploadColumn  = 'fORMFile::configureFileUploadColumn';
@@ -224,6 +226,31 @@


    /**
+    * Takes a directory and clears the temporary directory inside of it
+    * 
+    * @param  fActiveRecord $object            The fActiveRecord instance
+    * @param  array         &$values           The current values
+    * @param  array         &$old_values       The old values
+    * @param  array         &$related_records  Any records related to this record
+    * @param  array         &$cache            The cache array for the record
+    * @param  string        $method_name       The method that was called
+    * @param  array         $parameters        The parameters passed to the method
+    * @return void
+    */
+   static public function clearTempDir($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters)
+   {
+       $class = get_class($object);
+       
+       list ($action, $column) = fORM::parseMethod($method_name);
+       $column = preg_replace('#_temp_dir$#i', '', $column);
+       
+       $upload_dir = self::$file_upload_columns[$class][$column];
+       
+       self::prepareTempDir($upload_dir);
+   }
+   
+   
+   /**
     * Commits a transaction, or decreases the level
     * 
     * @internal
@@ -338,6 +365,12 @@
            self::prepare
        );

+       fORM::registerActiveRecordMethod(
+           $class,
+           'clear' . $camelized_column . 'TempDir',
+           self::clearTempDir
+       );
+       
        fORM::registerReflectCallback($class, self::reflect);
        fORM::registerInspectCallback($class, $column, self::inspect);
        fORM::registerReplicateCallback($class, $column, self::replicate);

Support Mapping Column Names

Ticket #602 by @khamer:

Example Usage: when working with particularly decrepit databases with unusually decrepit column names like 'CSTFRSTNM' or non-underscore names like 'CreditCardNumber', it'd be nice to provide better versions ('first_name' or 'credit_card_number') so that referencing the columns are more human. For example, getCstfrstnm vs getFirstName, or encodeCreditcardnumber vs encodeCardCardNumber. I'm not sure, but it'd be nice in where statements ( array('first_name=' => 'Kevin') instead of array('cstfrstnm=' => 'Kevin')).

Allow for Parameters for buildRelated Operations

Ticket #619 by @khamer:

It'd be nice to be able to pass in arguments to buildRelated methods, such as

$users = $group->buildUsers(array('status=' => 'active'));

Along the same lines, it'd be nice to also support the other arguments (order, limit, page.)

Comment by @khamer:

I believe ticket #25 may be a dependency of this ticket.

Split out fORMHTML

Ticket #538 by @wbond:

fActiveRecord should have the prepare and encode method split out into a new class called fORMHTML.

Add flourish to packagist (npm like installer)

Ticket #740 by @mblarsen

Please consider adding flourish to http://packagist.org/ a npm like package/dependency installer.

Comment by @wbond

Yes, I am on board with doing this.

The first step, which I have been thinking about for a little while, is getting Flourish into an environment like GitHub? so it is easier to collaborate. While I like a lot of what the current Flourish site offers, it's custom nature means that it is harder to let people contribute without spending a bunch of time trying to make sure they can't edit other files on the server.

I am hoping to move the main development of Flourish to GitHub? and set up a repo there with the documentation and hopefully even set up flourishlib.github.com to host the website. That way it will be easy for people to send pull requests with fixes and the collaboration model will be more open and sustainable.

In addition, this means changing to a semantic versioning system instead of using build numbers, especially since Git doesn't provide revision numbers. There will be a bunch of changes and tweaks that will need to be made like coming up with a better and more modern way to generate the docs (perhaps DocBlox??) and probably converting the wiki documentation over to markdown or something else that has modern tooling for easy generation.

I will be looking for help from people along the way since I am currently in a position where unfortunately I've committed to too many projects and can't keep up with the needs of all of them.

Comment by @mblarsen

The Packagist /getcomposer.org works perfectly with GitHub?. It both support auto versioning but can use tags as well and control it yourself. (testing it out with the small fFixture? extension I posted a few days back).

I'm impressed by the flourishlib site and it shows that you've put a lot of time into it - but there are other things, new projects, so I understand why you want to lift the burden on the 'practical stuff'. GitHub? is definitely a good way to go. I really like the URLs for the documentation, so I'm concerned if this could live on on github. Put perhaps a conversion if you docs into some simple markup mixed something like this TOC script http://projects.jga.me/toc/ could be a solution.

Thumbs of for making it more upen to pull requests.

Add enough parity for CASE statements in ORDERs so that raw SQL is no longer needed

Ticket #603 by @khamer:

Perhaps an ordered array of conditions?

This allows for order statements to no longer depend no the database for execution, which means build related methods could have a method signature that allows for conditions, orders, and limits. While there's some risk of these build methods leaking more schema into views/controllers, it'll hopefully mostly replace the more common and worse $record->buildRelatedRecords()->filter($conditions).

Store data in VARCHAR/TEXT columns as any type of format

Similar to this class which stores the data as a JSON string in the column, but it can be an array or object and there is no need to manually encode or decode to JSON.

https://github.com/tatsh/sutra/blob/master/classes/sORMJSON.php#L31

Should be similar things for serialize(), XML, YAML, and any other formats that might come about.

fORMJSON - array or keyed array or stdClass object
fORMXML - fXML object
fORMYAML - fYAML object (when that class is made)
fORMSerializable - unserialize() on out, serialize() called when being inserted

Fatal error when connect with sql server 2008

Ticket #744 by @phuocdaivl08

try {
    $db  = new fDatabase('mssql', 'TEST', 'sa', '123456','PHUOCDAI-PC\SQLEXPRESS');
    // Please note that calling this method is not required, and simply
    // causes an exception to be thrown if the connection can not be made
    $db->connect();

} catch (fAuthorizationException $e) {
    $e->printMessage();
}

Fatal error: Uncaught exception 'fConnectivityException' with message 'Unable to connect to database - hostname not found' in C:\AppServ?\inc\flourish\fDatabase.php:2057 Stack trace: #0 C:\AppServ?\inc\flourish\fDatabase.php(797): fDatabase→handleConnectionErrors(Array) #1 C:\AppServ?\www\posttin\c.php(31): fDatabase→connect() #2 {main} thrown in C:\AppServ?\inc\flourish\fDatabase.php on line 2057

Can you help me? How to fix this error? I'm from VietNam?. My English is not good!

Create fYaml

Ticket #540 by @wbond:

It would be nice to have a Yaml class.

Comment by @mblarsen:

Indeed - I use this one:

http://components.symfony-project.org/yaml/

It works great, however I would like the parser and dumper to be the samme class.

The inline-notation-level is a good idea.

Comment by @ilo:

The Yaml component of symfoni is licensed as MIT also, Will, could this be of help to integrate into florusihlib?

Comment by @wbond:

Yes, I use sfYaml on a side project. I did a little bit of work refactoring it into a single class, but I gave up in the interest of trying to get closer to shipping the project. Ideally it would be a single class that supports encoding and decoding, probably with convenience functions for loading and saving.

I think the main thing that needs to be identified here is what version of Yaml, or what functionality, should be supported. Also, I would think that fYaml would use any available C extensions for speed, but provide a PHP fallback.

Comment by @netcarver:

FWIW, I've used spyc before. It's a pure PHP implementation of a single-file YAML 1.0 compatible class available under the MIT license.

fRegex Class

Ticket #440 by vxnick:

Just a wild idea - have a class with common regular expressions, such as the following:

  • Validate Domain
  • Validate Email Address
  • Validate IP Address (with option to exclude private ranges such as 192.168.1.0/24, etc)

Some of these already exist in fValidation (and fAuthorization from memory), but it might be nice to split them out so we can just get a true/false return value.

Comment by @wbond:

Rather than pulling these all into a new class, I think I will take the model of fEmail, and make some regular expressions as constants on the applicable classes so that they can be reused.

fEmail - Messege-ID contains whitespaces

Ticket #753 by @toonman

Mail client could not display embedded images in emails… Apparently the added spaces in the Message-ID caused this. Sample Message-ID: <e3ccb8f9ec.1335371032@Linux infong 2.4 #1 SMP Tue Jan 17 02:58:41 UTC 2012 i686 GNU/Linux>

I changed the fEmail line 626 from this $this→message_id = '<' . fCryptography::randomString(10, 'hexadecimal') . '.' . time() . '@' . self::getFQDN() . '>';

to this… $this→message_id = '<' . fCryptography::randomString(10, 'hexadecimal') . '.' . time() . '@' . fURL::makeFriendly( self::getFQDN() ) . '>';

By adding the fURL::makefriendly(). This removed the whitespaces and the image finally showed up in Thunderbird and Outlook. Maybe it should be added with the next update.

Thanks for you great work!

Default owner/group & permissions for file operations

Ticket #202 by vena:

It'd be wonderful if we could have a set once and forget method to default owner/group and permissions for files created by fFile, fORMFile, etc. Outside of an suPHP environment, this can be quite necessary if you ever want to manipulate files created by the script by hand.

Comment by @wbond:

Due to the way that file permission are set up, the owner of a file can only be changed by the superuser. The group can only be changed to a group that the user, that the web server is running as, is part of. Because of those limitation it doesn't look like it will be possible to set that up.

In terms of the file permissions, I think I can come up with something. Currently fUpload chmods all uploaded files to 0644, but it would be good for fFile::create() and fDirectory::create() to all obey one set of permissions.

fValidation flexibility.

Ticket #750 by @netcarver

Currently the member variables and the check* methods are all declared private, making it impossible to extend the class and override any of those methods or access the member variables. Making these protected would allow extension if needed.

Comment by @audvare

I'd really like to know what you intend to do in your extension. I think the check* methods are private for good reason and I have yet to have a need to mess with fValidation's internals or extend on it.

fValidation has fValidation::addCallbackRule() which essentially allows custom validation on a per-object basis. And as such, you can simply create constants that define callbacks (so they look nicer in code).

class User extends fActiveRecord {
  const nameIsAvailable = 'User::nameIsAvailable';

  public static function nameIsAvailable($name) {
    try {
      new self(array('name' => $name));
      return FALSE; // is taken
    }
    catch (fNotFoundException $e) {}
    return TRUE;
  }
}

// later code
try {
  $validation = new fValidation;
  $validation->addRequiredFields('name');
  $validation->addCallbackRule('name', User::nameIsAvailable, 'Sorry, that user name is taken.');
  $validation->validate();
  fURL::redirect('/');
}
catch (fValidationException $e) {
  fMessaging::create('validation', '/registration' $e->getMessage());
  fURL::redirect('/registration');
}

I have a longer example here which uses fValidation: https://github.com/tatsh/sutra/wiki/Tutorial

Comment by @netcarver

Hello Andrew,

Thank you for reply and the great examples, especially in your link which I found particularly interesting.

My request is motivated by wanting to have callback rules be able to return an error message directly, rather than having to define it when the rule is added to the validation object. This would allow callbacks to provide messages that are appropriate to, say, some value of an input or other combinations of factors.

An additional motivation is the supply of custom strings to required elements such that the message displayed for, say, a missing age can be different to that displayed for a missing check box confirming acceptance of conditions. Whilst this can probably be done using a compose callback on fText, it might be easier to have a version of addRequiredFields() that accepts an array with key=>value pairs that match form element names to messages.

Comment by @audvare

The fValidaion::validate() method can also be controlled to not throw and instead return messages by passing TRUE as the first parameter. Passing TRUE as the second parameter removes filed names from the messages.

fValidationException goes through fText::compose(). You can use fText::registerCoposeCallback() in order to change messages. I know this may not be ideal for the situation you describe here.

You may instead need to skip fValidation for that purpose, still use fValidation but after you run fValidation::validate() perform more validation. If you do it in the try/catch block like in my example, then you can simply throw a new fValidationException when an error occurs (or of course in the method/funcion you call from there).

try {
  $validation = new fValidation;
  // Add conditions
  $validation->validate();

  // Method that throws with a specific message
  SomeClass::validateAdditionalConditions();
}
catch (fValidationException $e) {
  // Do something based on the error
}

You would not have to use fText::compose() as fValidation has fValidation::addStringReplacement() which makes str_replace() get called before each message is about to be composed.

I like the idea of specifying messages for required fields more easily. I suggest 2 solutions.

It would be interesting to have a method like fValidation::setRequiredFieldMessages(), basically an easier way than calling fValidation::addStringReplacement() many times and far easier to use since you don't have to know what to replace.

$validation = new fValidation;
$validation->setRequiredFields('accepted'); // checkbox
$validation->setRequiredFieldMessages(array(
  'accepted' => 'You must accept the terms and conditions to register.',
));
$validation->validate();

Any messages not overridden will be left alone.

Or, since you can use an array with fValidation::addRequiredFields() then a few new signatures (honestly the former is probably a lot easier to maintain):

class fValidation() {
  public function addRequiredFields(array $fields, array $messages) {}
  public function addRequiredFields(array $fields_to_messages) {} // as you described
}

If you make a change directly to fValidation and it works, post a patch.

Do not know if every comment should be mentioned. Please take a look at it: http://flourishlib.com/ticket/750

pk collision in prebuild

Ticket #749 by @unstoppablecarl

when having tables: categories, records, categories_records. categories are in a many-to-many relationship with records icategories primary key is id records primary key is id

when calling $categories→prebuildRecords();

the id column is missing from the $row var in the fActiveRecord::loadFromResult() method

$pk_columns = $schema→getKeys($table, 'primary');

    foreach ($pk_columns as $column) {

// error happens here

    $value = $row[$column]; if ($value !== NULL && isset(self::$unescape_map[$class][$column])) {

        $value = $db→unescape(self::$unescape_map[$class][$column], $value);

    }

    $this→values[$column] = fORMobjectify($class, $column, $value); unset($row[$column]);

    }

it seems that this bit of code in the fRecordSet::prebuild() method is the cause I don't quite understand what it's purpose is. // If we are going through a join table we need to remove the related primary key that was used for matching

    if (isset($relationshipjoin_table?)) {

        unset($row[$relationshipcolumn?]);

    }

fORMRequest

Ticket #537 by @wbond:

Make fORMRequest to hold the populate() method, and more methods for secure filtering of populate and population of individual columns.

Comment by @benhaines:

Attached above is a draft of the fORMRequest which i use currently, its not set up to work with relationships yet tho. Hope it helps with getting started with this integrated into the core flourish library, you can either whitelist or blacklist columns to be used with populate.

Attached file:

<?php 

    class fORMRequest{

        const populate  = 'fORMRequest::populate';
        const hydrate  = 'fORMRequest::hydrate';
        const addProtectedColumns  = 'fORMRequest::addProtectedColumns';
        const addAccessibleColumns  = 'fORMRequest::addAccessibleColumns';

        static private $protected_columns = array();

        static public function extend(){
            fORM::registerActiveRecordMethod(
                '*',
                'populate',
                self::populate
            );

            fORM::registerActiveRecordMethod(
                '*',
                'hydrate',
                self::hydrate
            );
        }

        static public function hydrate($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters){
            $class = get_class($object);
            $new_values = $parameters[0];

            fORM::callHookCallbacks(
                $object,
                'pre::hydrate()',
                $values,
                $old_values,
                $related_records,
                $cache
            );

            if(!is_array($new_values)){
                throw fProgrammerException(
                    'The hydrate method requires an associated array to populate the values'
                );
            }

            $schema = fORMSchema::retrieve($class);
            $table  = fORM::tablize($class);
            $new_value_keys = array_keys($new_values);
            $column_info = $schema->getColumnInfo($table);
            foreach ($column_info as $column => $info) {
                if (in_array($column,$new_value_keys)) {
                    $method = 'set' . fGrammar::camelize($column, TRUE);
                    $cast_to = ($info['type'] == 'blob') ? 'binary' : NULL;
                    $object->$method($new_values[$column]);
                }
            }

            fORM::callHookCallbacks(
                $object,
                'post::hydrate()',
                $values,
                $old_values,
                $related_records,
                $cache
            );

            return $object;
        }

        static public function populate($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters)
        {
            $class = get_class($object);

            fORM::callHookCallbacks(
                $object,
                'pre::populate()',
                $values,
                $old_values,
                $related_records,
                $cache
            );

            $schema = fORMSchema::retrieve($class);
            $table  = fORM::tablize($class);

            $column_info = $schema->getColumnInfo($table);

            foreach ($column_info as $column => $info) {
                //Ignore protected coulmns
                if(isset(self::$protected_columns[$class]) && in_array($column,self::$protected_columns[$class])){
                    continue;
                }
                if (fRequest::check($column)) {
                    $method = 'set' . fGrammar::camelize($column, TRUE);
                    $cast_to = ($info['type'] == 'blob') ? 'binary' : NULL;
                    $object->$method(fRequest::get($column, $cast_to));
                }
            }

            fORM::callHookCallbacks(
                $object,
                'post::populate()',
                $values,
                $old_values,
                $related_records,
                $cache
            );

            return $object;
        }

        static public function addProtectedColumns($class, $columns){

            $class = fORM::getClass($class);

            settype($columns, 'array');


            if (!isset(self::$protected_columns[$class])) {
                self::$protected_columns[$class] = array();
            }

            foreach ($columns as $column) {
                if(!in_array($column,self::$protected_columns[$class])){
                    self::$protected_columns[$class][] = $column;
                }   
            }
        }

        static public function addAccessibleColumns($class, $columns){

            $class = fORM::getClass($class);

            $schema = fORMSchema::retrieve($class);
            $table  = fORM::tablize($class);
            $column_info = $schema->getColumnInfo($table);
            $column_names = array_keys($column_info);

                settype($columns, 'array');

            if (!isset(self::$protected_columns[$class])) {
                self::$protected_columns[$class] = array();
            }

            foreach ($column_names as $column) {
                if(!in_array($column,$columns) && !in_array($column,self::$protected_columns[$class])){
                    self::$protected_columns[$class][] = $column;
                }   
            }
        }
    }

fODM, fODMDatabase, fDocument, fCollection

Ticket #568 by @mattsah:

This is a ticket to discuss and work on the possibility of adding document database support to Flourish. I have dome some initial implementation on my personal fork, however, it would be nice to see some support get mainlined into the trunk. With that said, here's a few things out of initial discussion with Will as well as additional research.

Aimed support:

  • MongoDB
  • CouchDB

MongoDB is much better suited for the proposed structure than CouchDB, namely because it has a built in concept of Collections as well as being the only document database (not simply key value store) with official PHP support it seems. Additionally there are aspects to the API that map well to the existing fDatabase API, although albeit, without SQL.

It seems as though there are two approaches which could make this work cleanly, although one would likely add significantly more code and repeat similar logic found in fORM, fActiveRecord, etc.

  1. It is possible to create a completely alternative Document driven Object Mapper independent of the common ORM/Relational base with classes such as those listed above.

  2. It is possible to centralize and abstract similar code into additional base classes, and then simply have fODM and fORM, for example, using the common base class for common logic. Similarly, fDatabase would SQL related tasks moved to fRelationalDatabase or fRDatabase while the specific but similar fDatabase API would use fDocumentDatabase or fDDatabase.

Benefits of the first approach is that there will be no need to independently check whether we are dealing with ORM or ODM in the base classes. This means someone could completely and independently implement fODM from any fORM related code. Major downsides is that a number of classes would have to be re-implemented. While Document Databases tend not to have a defined schema or SQL support, using a number of similar methods to what is allowed in normal fORM operations, one could easily create a number of validation and enforcement rules for schema-less databases, while the existing fORM code works with the schema validation as well as additional validation or ORM rules applied on top. This would mean on adding relevant classes for EVERY similar aspect: fODMNumber, fODMFile, fODMValidation, etc. I count about 13 new classes at least given the current size of the ORM count.

The benefit of the second approach would be that we could concentrate similar functionality and api in more core classes — for example fMapper, fMapperNumber, fMapperFile, fMapperValidation — or something similar. From here only the specific differences could be added into fORM and fODM prefixed classes. One major downside of this, of course, is huge breaks in backwards compatibility.

A mixed approach could add base classes + all related prefixed classes which would simply wrap the base packages in the initial API. Keeping the core API semantic would be ideal, so for example fORMtablize could be abstracted into fMapperclassToPool, from which fORMtablize and fODMcollectionize would simply call that.

This would allow for consistent but semantic naming along with limited backwards compatibility breaks. Many fORM vs. fODM method names would be similar and follow similar API but would reflect the differences between core terminology, meaning if you know one you could work with the other very easily, but it would also remain intuitive to each class set.

On the issue of the databases themselves, part of the problem remains their disparate API as well as lacking support in PHP. For example, while MongoDB has a built in concept of collections, CouchDB does not, and it is up to the author to group documents via a property. This being said, one approach might be to make fDocument non-abstract, so that a Document is a Document is a Document, if when instantiated the class of $this is not fDocument, additional steps could be taken to work within the approriate collection for MongoDB or similar databases, while in the case of CouchDB a property could be added to the document automatically to specify its collection.

CouchDB, does, however, provide the concept of views, which could for example be established as such:

function(doc) {
    if (doc.Collection == "collection_name") {
        emit(null, doc)
    }
}

However, from what I can tell there is no way to determine which views would be related to simple collections vs. which represent more complex map functions. In this sense, despite not having a schema, CouchDB is even less "schema-less" than MongoDB, as with MongoDB it is possible to list/show collections at least similar to getting a list of tables.

These are some initial ideas on the subject, and I will likely continuing developing some of them on my own fork to see what seems to work and what doesn't with more updates to come.

fmailbox enhancement: email softing

Ticket #623 by @chinvoon

hi,

I am just using fmailbox this class and found that it not bad, but poor is, it cannot do the softing by date, from or others. If allow i hope this function available in this class also.

And also, it cannot return total messages, but i already fix it and the coding are copy from your source also.

have a nice day.

Regards Tan Chin Voon

Use phpseclib for pure-php mcrypt replacement in fCryptography

Ticket #515 by @passcod:

See http://flourishlib.com/discussion/2/304

fCryptography should check for phpseclib's AES and Rijndael if mcrypt isn't found, and use these methods.

Comment by @passcod:

Here is an implementation over version 1.0.0b12 of fCryptography.

There is one significant issue to this implementation: data encrypted by phpseclib cannot be decrypted using mcrypt, and vice-versa. For this reason, instead of 'fCryptographysymmetric', when a string is encrypted using phpseclib the ciphertext prefix is 'fCryptographysymmetric-php'. Furthermore, the class will not attempt to decrypt a ciphertext not prefixed by the '-php' variant using phpseclib, nor will it try the inverse.

Another change made is to remove the dependency on mcrypt for key and iv generation, however this change should not affect de/encryption of existing data.

Comment by @wbond:

It appears that php sec lib does not provide cipher feedback mode (CFB) for its Rujndael implementation. Your code should be throwing strict error notices since you are using an undefined constant. This also explains why you were unable to produce results that are compatible with mcrypt.

To accomplish a proper pure-PHP fallback, Flourish is going to need to find or write a CFB mode AES implementation.

Comment by @wbond:

Upon further inspection, I found that phpseclib provides a CFB mode in SVN, just not in the release on their homepage. Also, you don't seem to be setting the same block and key sizes that mcrypt is using, which is probably why you are getting different results.

For me to accept your patch as the basis for the modifications to fCryptography, I just need to to fill out a contributor license agreement so that users of Flourish can be assured you are providing your code under the MIT license. If you wrote the code while working for another company, your company will need to fill out a corporate contributor license agreement and list you. If this is the case, you do not need to submit a CLA of your own.

Once you email me a PDF of the signed document, I can work on integrating your changes and testing it all.

Comment by @passcod:

Replying to wbond:
Also, you don't seem to be setting the same block and key sizes that mcrypt is using, which is probably why you are getting different results.

In fact, I do, I used the following piece of code to find the key/iv/block sizes used by mcrypt and hardcoded the values in:

$module   = mcrypt_module_open('rijndael-192', '', 'cfb', '');

$key_size = mcrypt_enc_get_key_size($module);
$iv_size = mcrypt_enc_get_iv_size($module);
$block_size = mcrypt_enc_get_block_size($module);

echo "\n\n\nKEY: $key_size\nIV: $iv_size\nBLOCK: $block_size";

The result being:

KEY: 32
IV: 24
BLOCK: 24

I had read the Crypt code and left the block size to default to 24. However, for better design, I now explicitly state the block size on initialization of Crypt_Rijndael.

You will notice the IV has a length of 24, when I use ::randomString to create an alphanumeric string of 32 characters. As the code shows, I base64 decode the string to give a 24 bytes IV. (This could be improved by allowing the user to specify a custom alphabet to ::randomString and therefore using the full base64 alphabet.)

Comment by @wbond:

The default block size in Crypt_Rijndael is 16, but I see you updated your code to specify 24. However, your key size is still wrong. mcrypt uses a 32 byte, or 256 bit key, however you are explicitly setting the key size to 192. I've updated the fCryptography documentation to explicitly state the block and key sizes.

I also added a base64 option to fCryptography::randomString() for the purpose of generating raw bytes with base64_decode().

Comment by @passcod:

Third rev.

I now use the base64 option for ::randomString(), and I have fixed the key and block sizes.

The cipher is still not compatible phpseclib/mcrypt (tested as: encrypt using php, decrypt using mcrypt).

FYI, here's my test code:

<?php

function __autoload($class_name)
{
    if ( preg_match("/^f.+$/i", $class_name) ) {
        $class_root = $_SERVER['DOCUMENT_ROOT'] . '/lib/flourish/';
    }
    else {
        $class_root = $_SERVER['DOCUMENT_ROOT'] . '/lib/class/';
    }
    $file = $class_root . $class_name . '.php';

    if (file_exists($file)) {
        include $file;
        return;
    }

    throw new Exception('The class ' . $class_name . ' could not be loaded');
}

header('Content-type: text/plain');

$c = new Crypt_Rijndael(); unset($c);
// Load phpseclib using __autoload function

$secret = "secret";
$pw = "password";

$ciphertext = fCryptography::symmetricKeyEncrypt($secret, $pw);

echo "$ciphertext\n";
echo "fCryptography::symmetric-php^xxxxxxxxxxxxxxIVxxxxxxxxxxxxxxxx^xCIPHERx^xxxxxxxxxxxxxxxxxxHMACxxxxxxxxxxxxxxxxxx \n\n";
// This is a mask to check the form of the ciphertext

$ciphertext = str_replace('-php', '', $ciphertext);

$plain = fCryptography::symmetricKeyDecrypt($ciphertext, $pw);

echo "\nPHP-MCRYPT TEST:\n\n$ciphertext\n";
echo "fCryptography::symmetric^xxxxxxxxxxxxxxIVxxxxxxxxxxxxxxxx^xCIPHERx^xxxxxxxxxxxxxxxxxxHMACxxxxxxxxxxxxxxxxxx";
//echo "\n\n= $plain";
// (Uncomment to test)
?>

Comment by @wbond:

This implies to me that there is a bug in the phpseclib code for CFB mode or the Rijndael cipher, however I can't say for sure. I'll have to spend a little time looking into it to see if I can figure out what is going on.

By the way, thanks for all of your work!

Comment by zelnaga:

Looking over phpseclib's Crypt/AES.php, I see that the author does reimplement CFB mode, where mcrypt is concerned, per the following:

http://phpseclib.sourceforge.net/cfb-demo.phps

So what might appear to be a bug in phpseclib might actually be a bug in mcrypt. And that mcrypt could have bugs should not be a total surprise. mcrypt is, after all, abandoned. Unless I'm mistaken, the last commit was almost two years ago to the day:

http://mcrypt.cvs.sourceforge.net/viewvc/mcrypt/mcrypt/

Here's another bug on php.net:

http://bugs.php.net/51146

Comment by zelnaga:

I also note that your calls to mcrypt_module_open use 'cfb' as the mode whereas what phpseclib is using is more analogous (although not quite identical for reasons already explained) to 'ncfb'.

Also, I thought this had already been discussed before, but why not ask the phpseclib author if they'd be willing to relicense - or at least dual license - their code under the MIT license and just include it? ie. let phpseclib use mcrypt when it's available instead of having the person using flourishlib have to make the decision for themselves?

Comment by zelnaga:

I sent the phpseclib maintainer an email suggesting they relicense their code to the MIT license and they agreed:

http://phpseclib.svn.sourceforge.net/viewvc/phpseclib?revision=130&view=revision

Comment by @wbond:

@zelnaga

Thanks for the work you've done looking into this. I read through the links you provided. The first one didn't seem to give any definitive info as to if mcrypt was doing anything wrong - instead it seemed to be three people unsure.

I did read the PHP bug report, and I think I am going to take a little time to read through some of the mcrypt source and the phpseclib source to see where they differ. Thanks to your request to relicense phpseclib, it looks like if I can get them functioning the same I could integrate them together into Flourish, which would be awesome for portability. If this ends up being the case, I think the only non-standard PHP extension required of Flourish would be openssl for secure connections.

Attached file:

<?php
/**
 * Provides cryptography functionality, including hashing, symmetric-key encryption and public-key encryption
 * 
 * @copyright  Copyright (c) 2007-2010 Will Bond
 * @author     Will Bond [wb] <[email protected]>
 * @license    http://flourishlib.com/license
 * 
 * @patch      passcod [passcod] <[email protected]>
 *
 * @package    Flourish
 * @link       http://flourishlib.com/fCryptography
 * 
 * @version    1.0.0b14
 * @changes    1.0.0b14  Added support for phpseclib's pure-php symmetric de/encryption [passcod, 2010-11-07]
 * @changes    1.0.0b13  Updated documentation about symmetric-key encryption to explicitly state block and key sizes, added base64 type to ::randomString() [wb, 2010-11-06]
 * @changes    1.0.0b12  Fixed an inline comment that incorrectly references AES-256 [wb, 2010-11-04]
 * @changes    1.0.0b11  Updated class to use fCore::startErrorCapture() instead of `error_reporting()` [wb, 2010-08-09]
 * @changes    1.0.0b10  Added a missing parameter to an fProgrammerException in ::randomString() [wb, 2010-07-29]
 * @changes    1.0.0b9   Added ::hashHMAC() [wb, 2010-04-20]
 * @changes    1.0.0b8   Fixed ::seedRandom() to pass a directory instead of a file to [http://php.net/disk_free_space `disk_free_space()`] [wb, 2010-03-09]
 * @changes    1.0.0b7   SECURITY FIX: fixed issue with ::random() and ::randomString() not producing random output on OSX, made ::seedRandom() more robust [wb, 2009-10-06]
 * @changes    1.0.0b6   Changed ::symmetricKeyEncrypt() to throw an fValidationException when the $secret_key is less than 8 characters [wb, 2009-09-30]
 * @changes    1.0.0b5   Fixed a bug where some windows machines would throw an exception when generating random strings or numbers [wb, 2009-06-09]
 * @changes    1.0.0b4   Updated for new fCore API [wb, 2009-02-16]
 * @changes    1.0.0b3   Changed @ error suppression operator to `error_reporting()` calls [wb, 2009-01-26]
 * @changes    1.0.0b2   Backwards compatibility break - changed ::symmetricKeyEncrypt() to not encrypt the IV since we are using HMAC on it [wb, 2009-01-26]
 * @changes    1.0.0b    The initial implementation [wb, 2007-11-27]
 */
class fCryptography
{
    // The following constants allow for nice looking callbacks to static methods
    const checkPasswordHash   = 'fCryptography::checkPasswordHash';
    const hashHMAC            = 'fCryptography::hashHMAC';
    const hashPassword        = 'fCryptography::hashPassword';
    const publicKeyDecrypt    = 'fCryptography::publicKeyDecrypt';
    const publicKeyEncrypt    = 'fCryptography::publicKeyEncrypt';
    const publicKeySign       = 'fCryptography::publicKeySign';
    const publicKeyVerify     = 'fCryptography::publicKeyVerify';
    const random              = 'fCryptography::random';
    const randomString        = 'fCryptography::randomString';
    const symmetricKeyDecrypt = 'fCryptography::symmetricKeyDecrypt';
    const symmetricKeyEncrypt = 'fCryptography::symmetricKeyEncrypt';


    /**
     * Checks a password against a hash created with ::hashPassword()
     * 
     * @param  string $password  The password to check
     * @param  string $hash      The hash to check against
     * @return boolean  If the password matches the hash
     */
    static public function checkPasswordHash($password, $hash)
    {
        $salt = substr($hash, 29, 10);

        if (self::hashWithSalt($password, $salt) == $hash) {
            return TRUE;
        }

        return FALSE;
    }


    /**
     * Create a private key resource based on a filename and password
     * 
     * @throws fValidationException  When the private key is invalid
     * 
     * @param  string $private_key_file  The path to a PEM-encoded private key
     * @param  string $password          The password for the private key
     * @return resource  The private key resource
     */
    static private function createPrivateKeyResource($private_key_file, $password)
    {
        if (!file_exists($private_key_file)) {
            throw new fProgrammerException(
                'The path to the PEM-encoded private key specified, %s, is not valid',
                $private_key_file
            );
        }
        if (!is_readable($private_key_file)) {
            throw new fEnvironmentException(
                'The PEM-encoded private key specified, %s, is not readable',
                $private_key_file
            );
        }

        $private_key          = file_get_contents($private_key_file);
        $private_key_resource = openssl_pkey_get_private($private_key, $password);

        if ($private_key_resource === FALSE) {
            throw new fValidationException(
                'The private key file specified, %s, does not appear to be a valid private key or the password provided is incorrect',
                $private_key_file
            );
        }

        return $private_key_resource;
    }


    /**
     * Create a public key resource based on a filename
     * 
     * @param  string $public_key_file  The path to an X.509 public key certificate
     * @return resource  The public key resource
     */
    static private function createPublicKeyResource($public_key_file)
    {
        if (!file_exists($public_key_file)) {
            throw new fProgrammerException(
                'The path to the X.509 certificate specified, %s, is not valid',
                $public_key_file
            );
        }
        if (!is_readable($public_key_file)) {
            throw new fEnvironmentException(
                'The X.509 certificate specified, %s, can not be read',
                $public_key_file
            );
        }

        $public_key = file_get_contents($public_key_file);
        $public_key_resource = openssl_pkey_get_public($public_key);

        if ($public_key_resource === FALSE) {
            throw new fProgrammerException(
                'The public key certificate specified, %s, does not appear to be a valid certificate',
                $public_key_file
            );
        }

        return $public_key_resource;
    }


    /**
     * Provides a pure PHP implementation of `hash_hmac()` for when the hash extension is not installed
     * 
     * @internal
     * 
     * @param  string $algorithm  The hashing algorithm to use: `'md5'` or `'sha1'`
     * @param  string $data       The data to create an HMAC for
     * @param  string $key        The key to generate the HMAC with 
     * @return string  The HMAC
     */
    static public function hashHMAC($algorithm, $data, $key)
    {
        if (function_exists('hash_hmac')) {
            return hash_hmac($algorithm, $data, $key);
        }

        // Algorithm from http://www.ietf.org/rfc/rfc2104.txt
        if (strlen($key) > 64) {
            $key = pack('H*', $algorithm($key));
        }
        $key  = str_pad($key, 64, "\x0");

        $ipad = str_repeat("\x36", 64);
        $opad = str_repeat("\x5C", 64);

        return $algorithm(($key ^ $opad) . pack('H*', $algorithm(($key ^ $ipad) . $data)));
    }


    /**
     * Hashes a password using a loop of sha1 hashes and a salt, making rainbow table attacks infeasible
     * 
     * @param  string $password  The password to hash
     * @return string  An 80 character string of the Flourish fingerprint, salt and hashed password
     */
    static public function hashPassword($password)
    {
        $salt = self::randomString(10);

        return self::hashWithSalt($password, $salt);
    }


    /**
     * Performs a large iteration of hashing a string with a salt
     * 
     * @param  string $source  The string to hash
     * @param  string $salt    The salt for the hash
     * @return string  An 80 character string of the Flourish fingerprint, salt and hashed password
     */
    static private function hashWithSalt($source, $salt)
    {
        $sha1 = sha1($salt . $source);
        for ($i = 0; $i < 1000; $i++) {
            $sha1 = sha1($sha1 . (($i % 2 == 0) ? $source : $salt));
        }

        return 'fCryptography::password_hash#' . $salt . '#' . $sha1;
    }


    /**
     * Decrypts ciphertext encrypted using public-key encryption via ::publicKeyEncrypt()
     * 
     * A public key (X.509 certificate) is required for encryption and a
     * private key (PEM) is required for decryption.
     * 
     * @throws fValidationException  When the ciphertext appears to be corrupted
     * 
     * @param  string $ciphertext        The content to be decrypted
     * @param  string $private_key_file  The path to a PEM-encoded private key
     * @param  string $password          The password for the private key
     * @return string  The decrypted plaintext
     */
    static public function publicKeyDecrypt($ciphertext, $private_key_file, $password)
    {
        self::verifyPublicKeyEnvironment();

        $private_key_resource = self::createPrivateKeyResource($private_key_file, $password);

        $elements = explode('#', $ciphertext);

        // We need to make sure this ciphertext came from here, otherwise we are gonna have issues decrypting it
        if (sizeof($elements) != 4 || $elements[0] != 'fCryptography::public') {
            throw new fProgrammerException(
                'The ciphertext provided does not appear to have been encrypted using %s',
                __CLASS__ . '::publicKeyEncrypt()'
            );
        }

        $encrypted_key = base64_decode($elements[1]);
        $ciphertext    = base64_decode($elements[2]);
        $provided_hmac = $elements[3];

        $plaintext = '';
        $result = openssl_open($ciphertext, $plaintext, $encrypted_key, $private_key_resource);
        openssl_free_key($private_key_resource);

        if ($result === FALSE) {
            throw new fEnvironmentException(
                'There was an unknown error decrypting the ciphertext provided'
            );
        }

        $hmac = self::hashHMAC('sha1', $encrypted_key . $ciphertext, $plaintext);

        // By verifying the HMAC we ensure the integrity of the data
        if ($hmac != $provided_hmac) {
            throw new fValidationException(
                'The ciphertext provided appears to have been tampered with or corrupted'
            );
        }

        return $plaintext;
    }


    /**
     * Encrypts the passed data using public key encryption via OpenSSL
     * 
     * A public key (X.509 certificate) is required for encryption and a
     * private key (PEM) is required for decryption.
     * 
     * @param  string $plaintext        The content to be encrypted
     * @param  string $public_key_file  The path to an X.509 public key certificate
     * @return string  A base-64 encoded result containing a Flourish fingerprint and suitable for decryption using ::publicKeyDecrypt()
     */
    static public function publicKeyEncrypt($plaintext, $public_key_file)
    {
        self::verifyPublicKeyEnvironment();

        $public_key_resource = self::createPublicKeyResource($public_key_file);

        $ciphertext     = '';
        $encrypted_keys = array();
        $result = openssl_seal($plaintext, $ciphertext, $encrypted_keys, array($public_key_resource));
        openssl_free_key($public_key_resource);

        if ($result === FALSE) {
            throw new fEnvironmentException(
                'There was an unknown error encrypting the plaintext provided'
            );
        }

        $hmac = self::hashHMAC('sha1', $encrypted_keys[0] . $ciphertext, $plaintext);

        return 'fCryptography::public#' . base64_encode($encrypted_keys[0]) . '#' . base64_encode($ciphertext) . '#' . $hmac;
    }


    /**
     * Creates a signature for plaintext to allow verification of the creator
     * 
     * A private key (PEM) is required for signing and a public key
     * (X.509 certificate) is required for verification.
     * 
     * @throws fValidationException  When the private key is invalid
     * 
     * @param  string $plaintext         The content to be signed
     * @param  string $private_key_file  The path to a PEM-encoded private key
     * @param  string $password          The password for the private key
     * @return string  The base64-encoded signature suitable for verification using ::publicKeyVerify()
     */
    static public function publicKeySign($plaintext, $private_key_file, $password)
    {
        self::verifyPublicKeyEnvironment();

        $private_key_resource = self::createPrivateKeyResource($private_key_file, $password);

        $result = openssl_sign($plaintext, $signature, $private_key_resource);
        openssl_free_key($private_key_resource);

        if (!$result) {
            throw new fEnvironmentException(
                'There was an unknown error signing the plaintext'
            );
        }

        return base64_encode($signature);
    }


    /**
     * Checks a signature for plaintext to verify the creator - works with ::publicKeySign()
     * 
     * A private key (PEM) is required for signing and a public key
     * (X.509 certificate) is required for verification.
     * 
     * @param  string $plaintext         The content to check
     * @param  string $signature         The base64-encoded signature for the plaintext
     * @param  string $public_key_file   The path to an X.509 public key certificate
     * @return boolean  If the public key file is the public key of the user who signed the plaintext
     */
    static public function publicKeyVerify($plaintext, $signature, $public_key_file)
    {
        self::verifyPublicKeyEnvironment();

        $public_key_resource = self::createPublicKeyResource($public_key_file);

        $result = openssl_verify($plaintext, base64_decode($signature), $public_key_resource);
        openssl_free_key($public_key_resource);

        if ($result === -1) {
            throw new fEnvironmentException(
                'There was an unknown error verifying the plaintext and signature against the public key specified'
            );
        }

        return ($result === 1) ? TRUE : FALSE;
    }


    /**
     * Generates a random number using [http://php.net/mt_rand mt_rand()] after ensuring a good PRNG seed
     * 
     * @param  integer $min  The minimum number to return
     * @param  integer $max  The maximum number to return
     * @return integer  The psuedo-random number
     */
    static public function random($min=NULL, $max=NULL)
    {
        self::seedRandom();
        if ($min !== NULL || $max !== NULL) {
            return mt_rand($min, $max);
        }
        return mt_rand();
    }


    /**
     * Returns a random string of the type and length specified
     * 
     * @param  integer $length  The length of string to return
     * @param  string  $type    The type of string to return: `'alphanumeric'`, `'alpha'`, `'numeric'`, or `'hexadecimal'`
     * @return string  A random string of the type and length specified
     */
    static public function randomString($length, $type='alphanumeric')
    {
        if ($length < 1) {
            throw new fProgrammerException(
                'The length specified, %1$s, is less than the minimum of %2$s',
                $length,
                1
            );
        }

        switch ($type) {
            case 'base64':
                $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/';
                break;

            case 'alphanumeric':
                $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
                break;

            case 'alpha':
                $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                break;

            case 'numeric':
                $alphabet = '0123456789';
                break;

            case 'hexadecimal':
                $alphabet = 'abcdef0123456789';
                break;

            default:
                throw new fProgrammerException(
                    'The type specified, %1$s, is invalid. Must be one of: %2$s.',
                    $type,
                    join(', ', array('alphanumeric', 'alpha', 'numeric', 'hexadecimal'))
                );
        }

        $alphabet_length = strlen($alphabet);
        $output = '';

        for ($i = 0; $i < $length; $i++) {
            $output .= $alphabet[self::random(0, $alphabet_length-1)];
        }

        return $output;
    }


    /**
     * Makes sure that the PRNG has been seeded with a fairly secure value
     * 
     * @return void
     */
    static private function seedRandom()
    {
        static $seeded = FALSE;

        if ($seeded) {
            return;
        }

        fCore::startErrorCapture(E_WARNING);

        $bytes = NULL;

        // On linux/unix/solaris we should be able to use /dev/urandom
        if (!fCore::checkOS('windows') && $handle = fopen('/dev/urandom', 'rb')) {
            $bytes = fread($handle, 4);
            fclose($handle);

        // On windows we should be able to use the Cryptographic Application Programming Interface COM object
        } elseif (fCore::checkOS('windows') && class_exists('COM', FALSE)) {
            try {
                // This COM object no longer seems to work on PHP 5.2.9+, no response on the bug report yet
                $capi  = new COM('CAPICOM.Utilities.1');
                $bytes = base64_decode($capi->getrandom(4, 0));
                unset($capi);
            } catch (Exception $e) { }
        }

        // If we could not use the OS random number generators we get some of the most unique info we can        
        if (!$bytes) {
            $string = microtime(TRUE) . uniqid('', TRUE) . join('', stat(__FILE__)) . disk_free_space(dirname(__FILE__));
            $bytes  = substr(pack('H*', md5($string)), 0, 4);
        }

        fCore::stopErrorCapture();

        $seed = (int) (base_convert(bin2hex($bytes), 16, 10) - 2147483647);

        mt_srand($seed);

        $seeded = TRUE;
    }


    /**
     * Decrypts ciphertext encrypted using symmetric-key encryption via ::symmetricKeyEncrypt()
     * 
     * Since this is symmetric-key cryptography, the same key is used for
     * encryption and decryption.
     * 
     * @throws fValidationException  When the ciphertext appears to be corrupted
     * 
     * @param  string $ciphertext  The content to be decrypted
     * @param  string $secret_key  The secret key to use for decryption
     * @return string  The decrypted plaintext
     */
    static public function symmetricKeyDecrypt($ciphertext, $secret_key)
    {
        $elements = explode('#', $ciphertext);

        // We need to make sure this ciphertext came from here, otherwise we are gonna have issues decrypting it
        if (sizeof($elements) == 4 || $elements[0] == 'fCryptography::symmetric' || $elements[0] == 'fCryptography::symmetric-php') {} else {
            throw new fProgrammerException(
                'The ciphertext provided does not appear to have been encrypted using %s',
                __CLASS__ . '::symmetricKeyEncrypt()'
            );
        }

        $iv            = base64_decode($elements[1]);
        $ciphertext    = base64_decode($elements[2]);
        $provided_hmac = $elements[3];

        $hmac = self::hashHMAC('sha1', $iv . '#' . $ciphertext, $secret_key);

        // By verifying the HMAC we ensure the integrity of the data
        if ($hmac != $provided_hmac) {
            throw new fValidationException(
                'The ciphertext provided appears to have been tampered with or corrupted'
            );
        }

        // This code uses the Rijndael cipher with a 192 bit block size and a 256 bit key in cipher feedback mode

        $key = substr(sha1($secret_key), 0, 32);

        if (class_exists('Crypt_Rijndael', false) && $elements[0] == 'fCryptography::symmetric-php') {
            // Initialize Rijndael
            $crypt = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CFB);
            $crypt->setKeyLength(256);
            $crypt->setBlockLength(24);
            $crypt->disablePadding();

            // Set key and iv
            $crypt->setKey($key);
            $crypt->setIV($iv);

            // Main decryption
            $plaintext = $crypt->decrypt($ciphertext);

            // Clean up
            unset($crypt);
        }
        elseif ($elements[0] == 'fCryptography::symmetric') {
            self::verifySymmetricKeyEnvironment();
            // Set up the main decryption, we are gonna use AES-192 (also know as rijndael-192) in cipher feedback mode
            $module   = mcrypt_module_open('rijndael-192', '', 'cfb', '');
            mcrypt_generic_init($module, $key, $iv);

            fCore::startErrorCapture(E_WARNING);
            $plaintext = mdecrypt_generic($module, $ciphertext);
            fCore::stopErrorCapture();

            mcrypt_generic_deinit($module);
            mcrypt_module_close($module);
        }
        else {
            if ($elements[0] == 'fCryptography::symmetric') {
                throw new fProgrammerException(
                    'The ciphertext provided cannot be decrypted because the required PHP extension %s is not installed',
                    'mcrypt'
                );
            }
            else {
                throw new fProgrammerException(
                    'The ciphertext provided cannot be decrypted because the required PHP library %s is not available',
                    'PHPSecLib (Crypt/Rijndael)'
                );
            }
        }

        return $plaintext;
    }


    /**
     * Encrypts the passed data using symmetric-key encryption
     *
     * Since this is symmetric-key cryptography, the same key is used for
     * encryption and decryption.
     * 
     * @throws fValidationException  When the $secret_key is less than 8 characters long
     *  
     * @param  string $plaintext   The content to be encrypted
     * @param  string $secret_key  The secret key to use for encryption - must be at least 8 characters
     * @return string  An encrypted and base-64 encoded result containing a Flourish fingerprint and suitable for decryption using ::symmetricKeyDecrypt()
     */
    static public function symmetricKeyEncrypt($plaintext, $secret_key)
    {
        if (strlen($secret_key) < 8) {
            throw new fValidationException(
                'The secret key specified does not meet the minimum requirement of being at least %s characters long',
                8
            );
        }

        // This code uses the Rijndael cipher with a 192 bit block size and a
        // 256 bit key in cipher feedback mode. Cipher feedback mode is chosen
        // because no extra padding is added, ensuring we always get the exact
        // same plaintext out of the decrypt method

        $key = substr(sha1($secret_key), 0, 32);
        $iv  = base64_decode(self::randomString(32, 'base64'));

        if (class_exists('Crypt_Rijndael', false)) {
            $phpseclib = true;
            // Initialize Rijndael
            $crypt = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CFB);
            $crypt->setKeyLength(256);
            $crypt->setBlockLength(24);
            $crypt->disablePadding();

            // Set key and iv
            $crypt->setKey($key);
            $crypt->setIV($iv);

            // Main encryption
            $ciphertext = $crypt->encrypt($plaintext);

            // Clean up
            unset($crypt);
        }
        else {
            $phpseclib = false;
            self::verifySymmetricKeyEnvironment();
            $module   = mcrypt_module_open('rijndael-192', '', 'cfb', '');


            // Finish the main encryption
            mcrypt_generic_init($module, $key, $iv);

            fCore::startErrorCapture(E_WARNING);
            $ciphertext = mcrypt_generic($module, $plaintext);
            fCore::stopErrorCapture();

            // Clean up the main encryption
            mcrypt_generic_deinit($module);
            mcrypt_module_close($module);
        }

        // Here we are generating the HMAC for the encrypted data to ensure data integrity
        $hmac = self::hashHMAC('sha1', $iv . '#' . $ciphertext, $secret_key);

        // All of the data is then encoded using base64 to prevent issues with character sets
        $encoded_iv         = base64_encode($iv);
        $encoded_ciphertext = base64_encode($ciphertext);

        // Indicate in the resulting encrypted data what the encryption tool was
        $return  = 'fCryptography::symmetric';
        $return .= $phpseclib ? '-php' : '';
        $return .='#' . $encoded_iv . '#' . $encoded_ciphertext . '#' . $hmac;

        return $return;
    }


    /**
     * Makes sure the required PHP extensions and library versions are all correct
     * 
     * @return void
     */
    static private function verifyPublicKeyEnvironment()
    {
        if (!extension_loaded('openssl')) {
            throw new fEnvironmentException(
                'The PHP %s extension is required, however is does not appear to be loaded',
                'openssl'
            );
        }
    }


    /**
     * Makes sure the required PHP extensions and library versions are all correct
     * 
     * @return void
     */
    static private function verifySymmetricKeyEnvironment()
    {
        if (!extension_loaded('mcrypt')) {
            throw new fEnvironmentException(
                'The PHP %s extension is required, however is does not appear to be loaded',
                'mcrypt'
            );
        }
        if (!function_exists('mcrypt_module_open')) {
            throw new fEnvironmentException(
                'The cipher used, %1$s (also known as %2$s), requires libmcrypt version 2.4.x or newer. The version installed does not appear to meet this requirement.',
                'AES-192',
                'rijndael-192'
            );
        }
        if (!in_array('rijndael-192', mcrypt_list_algorithms())) {
            throw new fEnvironmentException(
                'The cipher used, %1$s (also known as %2$s), does not appear to be supported by the installed version of libmcrypt',
                'AES-192',
                'rijndael-192'
            );
        }
    }


    /**
     * Forces use as a static class
     * 
     * @return fSecurity
     */
    private function __construct() { }
}



/**
 * Copyright (c) 2007-2010 Will Bond <[email protected]>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

Serialization of XML

Ticket #492 by @mattsah:

Are there any plans to add serialization of XML to fXML? It would be kinda cool if I could use fJSONencode and fXMLencode similarly. I understand there are conceptual differences between what these do, but it seems like fXMLencode is simple enough that it could just check if it was receiving a string and do it's normal string encoding… but if it were an object, perhaps it could iterate over the object creating elements for each of it's attributes or if it were an array of objects, multiple elements, returning a string of XML that could then be placed inside a root element.

It may also be cool to add support for pseudo toXML or toJSON methods for non-stdClass objects which could return custom stdClass objects with private properties etc.

Comment by @wbond:

There are a few issues I see with this:

  • What would the root element be for an array or object? and ?
  • What would the element be for items in an array? ?
  • Further, if an array while an element in an object, would the array element be the name of the object property or the same thing as a root-level array?
  • I found a jQuery plugin that does something similar to what you are looking for: http://michalkorecki.com/content/introducing-json-xml-jquery-plugin. I see the example falling down if "students" was an array instead of a object with a single property called "student" that was an array. The later seems like it would be a more common data structure.

    I wouldn't be opposed to adding such a thing if we can come up with a solid and intuitive set of rules for converting a JSON data structure to XML (just because we already know the PHP → JSON mapping rules).

    Comment by @mattsah:

    Some of what you raise is things that I was contemplating, and to me it all revolves around the question of why/when someone might be wanting to serialize something to XML. It seems to me both the API and recursion might benefit from an optional second parameter for use when encoding objects that would result in the string passed being used as the root element, so for example if I did the following:

    $users = array(
        array(
            'name' => 'Matthew J. Sahagian',
            'dob'  => 'April 28th, 1984'
        ),
        ...
    
    );
    fXMLencode($users, 'users');

    I might get something back such that:

    <users>
        <item index="0">
            <name>Matthew J. Sahagian</name>
            <dob>April 28th, 1984</dob>
        </item>
        <item index="1">
        ...
        </item>
    </user>

    If the parameter is left null, arrays could use simply "data" while objects could perhaps default to the underscorized class name, or only in the event of stdclass… data.

    Where keys/property names are used the element would obviously reflect these as in the above example.

    I think data / item are good defaults for roots + numerically index array elements.

    Obviously if someone creates an insanely nested array with no keys and then complains about all the recursive "data" elements, it is their own fault.

    In the event of a numeric key pointing to an array you might get something like:

    <data>
        <item index="0">
            <data>
                ...
            </data>
        </item>
    </data>

    As I mentioned when I opened, I feel the big question is going to be a matter of how it tends to be used by people… I think it's fairly rare that someone will be serializing non-associative arrays. And objects have the very useful distinction of having their property names to identify things. Perhaps this is at least a start to the concept? Obviously, the onus to create the desired format will be on the user giving keys/properties as appropriate to their arrays. As mentioned with a conception for _toJSON and _toXML (how do you escape tow underscores in this thing?) I feel this will likely get used the most in models, whereby the class name will be a good indicator as to what to call an element.

    $users = Users::build(array('username~' => 'matt'));
    fXML::encode($users->getRecords(), 'record_set');
    <record_set>
        <user>
            <first_name>Matthew</first_name>
            <last_name>Sahagian</last_name>
            ...
        </user>
        ...
    </record_set>

    Comment by @wbond:

    After talking with some different developers, here is the idea I have:

    1. The parameters for fXML::encode() would be: $value, $root_element_name='root', $array_item_element_name='item'
    2. $array_item_element_name could accept either a string, or an associative array of parent_element_name ⇒ array_item_elemenet_name for making custom tags in arrays
    3. Any array elements that have a key that is not a valid element name would become an item tag with an attribute name="{key}"
    4. Any array keys that start with @ would become attributes of the parent tag
    5. fXML::encode() would continue to accept scalar values, and would only use the new parameters for arrays or objects

    Does this sound like a solution that would work for you?

    Comment by @mattsah:

    This seems quite alright and as far as I can tell quite in line with what I originally had in mind. It seems in most cases that common XML subformats or microformats are fairly simple and I would imagine most people will be using them with appropriately structured objects or associative arrays, but it will be good to have the option to override both.

    With regards to scalar values, indeed!

    Comment by @wbond:

    When I was at PHP|Tek 11, one of the guys from Frapi did a presentation and talked about how they support both XML and JSON. There was some discussion about how they translated arrays into XML. Since they have some traction in the PHP API arena, I think it would be good to emulate them as long as it isn't too crazy.

final::validate() hook

Ticket #601 by @jeffturcotte:

Would be nice to have a validation hook that gets called AFTER message reordering and replacement. Alternatively, post::validate could be moved, or message reordering and replacement could simply use post::validate() instead of being standalone. But both of those require more work and potential compatibility breaks.

(for reference fActiveRecord, lines 2901-2903)

sqLite Latest Version w/ PHP 5.4

Ticket #741 by @rcrisman

I create an sqLite database using flourish but when I go to load the database I get

the sqlite file is version 3.7.7.1

php version 5.4

[05-Mar-2012 16:40:00 UTC] PHP Fatal error:  Uncaught exception 'fConnectivityException' with message 'The database specified does not appear to be a valid SQLite v2.1 or v3 database' in E:\public_html\inc\flourish\fDatabase.php:956
Stack trace:
#0 E:\public_html\inc\flourish\fDatabase.php(331): fDatabase->determineExtension()
#1 E:\public_html\inc\init.php(9): fDatabase->__construct('sqlite', 'E:\public_html/...')
#2 E:\public_html\index.php(2): include_once('E:\public_html\...')
#3 {main}
  thrown in E:\public_html\inc\flourish\fDatabase.php on line 956

Generate form based on table schema; fCRUD

Something like:

<?php
fCRUD::makeForm(string $orm_class, string $post_url, string $method = 'post', array $field_type_overrides = array(), array $unwanted_fields = array());
fCRUD::registerFormFieldCallback(string $orm_class_or_star, string $field_type, string $callback);
fCRUD::makeTableForm(string $orm_class, string $post_url, string $method = 'post', array $these_fields_only = NULL);
fCRUD::registerTableFormCallback(string $orm_class_or_star, string $column_name, string|array $callback, array $callback_args = array());
<?php
$html = fCRUD::makeForm($orm_class, '/place-to-post');

function cb($html, $attr = array()) {
  return '<div class="form-textfield-container"><input type="type" name="'.$attr['name'].'"></div>';
}
// Register a callback for all textfields rendered
fCRUD::registerFormFieldCallback('*', 'textfield', 'cb');

// Override some column types
$html = fCRUD::makeForm($orm_class, array('email_address' => 'email', 'user_password' => 'password'));

// Table with checkboxes
$html = fCRUD::makeTableForm($orm_class);

// Table with checkboxes, only the selected columns, callbacks, 2 signatures
fCrud::registerTableFormCallback($orm_class, 'date_created', array($fdate_obj, 'format'), array('U'));
$html = fCRUD::makeTableForm($orm_class, '/place-to-post', 'post', array('name', 'date_created'));

Buffer and include minified CSS+JS

Ticket #573 by @mattsah:

Since the minification mode acts as a wrapper to CSS + JS anyhow, would it be possible to start an output buffer and include the CSS and JS so that PHP could be inlined in CSS and Javascript, the get the contents of the buffer and write that out to the cache files? This would be an awesome enhancement for AJAX based Javascript as if you are using a routing library or something you'd be able to use your normal link methods rather than having to hard code URLs, etc.

Comment by @wbond:

I understand the general concept of what you are looking for. Could you write some example code that would show how you think it would best work?

Comment by @mattsah:

Will, in my code I have done this fairly simply — although it is not an independently configurable feature it just becomes a "built in feature" of the minfication:

Starting on line 779 of fTemplating I have changed the code in the else statement as follows:

ob_start();
    include $path;
    $minified_path = ob_get_clean();
    $minified_path = trim($this->minify($minified_path, $type));
    file_put_contents($path_cache_file, $minified_path);

What follows is a piece of my Javascript code which gets minified (sans callback function):

    jQuery.ajax({
        type   : 'GET',
        url    : <%= fJSON::encode(iw::makeLink(
            iw::makeTarget('PastebinController', 'comments'),
            array(
                ':hashid' => fRequest::get('hashid'),
                ':title'  => fRequest::get('title')
            )
        )) %>,
        success:  function(data){
            ...
        }
    });

You can see from the above example I am able to inline the PHP to work with the router to get the appropriate link for the comments on the particular Paste retrieved by my PastebinController? — the iw::makeLink() method works with the router to construct the appropriate link from the provided information — if I change that route, my Javascript will change nicely with it.

Here's the problems I see:

  1. It should be explicitly made clear that inlining PHP into Javascript or CSS files requires minification enabled — if there is a concern regarding speed for users who want minification but not parsing of PHP perhaps a method such as the following could be added:
$template = new fTemplating();
    $template->enableMinification('development', $cache_directory);
    $template->enableGlobalPHP();

An exception could then be thrown if enableGlobalPHP() was called without enabling Minification.

  1. It may be the case that someone wants to inline PHP in their CSS/JS without using minification — This would obviously require a separate mechanism for then parsing and caching the output of the individual CSS/JS files and using those. This, however, seems like it could be left for a later date only if user demand was there.

My thoughts were simply that because minification mode opens the file anyway and writes out to cache regardless, including would add little overhead, and a good amount of benefit for scenarios as pointed out above. It would also be useful for simple PHP enhancement of CSS.

<%= CSS::makeGradient(CSS::VERTICAL_GRADIENT, '#000', '#fff') %>

Output:

background-color: #000;
background: -moz-linear-gradient(top, #000 0%, #fff 100%); /* firefox */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #000), color-stop(100%, #fff)); /* webkit */
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#000', endColorstr='#fff', GradientType=0); /* ie */

Cleanup fURL method functionality/naming

Ticket #369 by vxnick:

Consider making fURL::get() return the entire URL - including the domain and query string (if set). Alternatively, add a new method to return the entire URL, perhaps fURL::getURL().

Comment by vxnick:

Perhaps consider adding fURL::getProtocol() to return http://, https://, ftp://, etc, and to save on manually checking whether SSL is set in $_SERVER or the port is 443, etc.

Comment by vxnick:

fURL::getAnchor() might be another useful method - return an anchor if it exists (some/url#anchor).

Comment by @wbond:

Yeah, it seems like there should be:

fURL::getProtocol() - returns http://, etc
fURL::getHost() - returns example.com, with any non-standard port
fURL::getPath() - the current fURL::get(), /path/to/script
fURL::getQuery() - return ?key=value

Unfortunately the anchor is never sent to the server, so we can't return that.

The other issue I see is methods that return a combination of the above parts. For instance the protocol and host are often used together (currently fURL::getDomain()). Perhaps fURL::get() should return the whole URL by default, but optionally allow the programmer to pass in the parts they want?

fURL::get() - the whole URL http://example.com:81/path/to/script?key=value
fURL::get('protocol', 'host') - http://example.com:81
fURL::get('path', 'query') - /path/to/script?key=value

Or perhaps all of the different methods should be discarded for a single fURL::get() which you can pass elements into? This would also make it so it could allow schema (http) and port (80) without extra methods.

Comment by vxnick:

I like the idea of passing elements into fURL::get() - that seems like a really nice solution.

Comment by mantaworks:

The problem with passing elements in is that they might not go together. What's the expected output of fURLget('protocol', 'path') or fURLget('query', 'host')? There's ambiguity.

I'd say a method like fURL::getAllParts() or fURL::split() that returns an associative array with all the parts named would be the way to go. You'd still have the parts you're interested in displayed clearly, without being extremely verbose.

Comment by alexleeds:

According to the php parse_url function (http://php.net/manual/en/function.parse-url.php) it is possible to get the "fragment" (aka the part after the #)

parse_url('http://username:password@hostname/path?arg=value#anchor') returns:

Array
(
    [scheme] =&gt; http
    [host] =&gt; hostname
    [user] =&gt; username
    [pass] =&gt; password
    [path] =&gt; /path
    [query] =&gt; arg=value
    [fragment] =&gt; anchor
)

Comment by @wbond:

@alexleeds

parse_url() will return the fragment if you pass in a URL that contains one. The issue is that browsers don't send the fragment to the web server, so we can't get it.

Comment by @wbond:

@mantaworks

How about allowing only a single element to be specified, but with a < before or > after to indicate the element requested plus everything before it or everything after it?

// Would return http://example.com
fURL::get('<hostname');

// Would return /path/to/script?key=val
fURL::get('path>');

When working with URLs we are all looking for a way to get a single result usually containing the most specific, or least specific pieces.

Add BMP support to fImage, improve error messaging

Ticket #579 by @wbond:

fImage should be enhanced to support BMP images by adding a custom parser for BMPs and GD.

In addition, error messaging should be enhanced to tell the user what kind of file was provided if not an image file.

This is a result of ticket #578

Comment by doccie:

/*     * ****************************************** */
    /* Fonction: ImageCreateFromBMP              */
    /* Author:   DHKold                          */
    /* Contact:  [email protected]                */
    /* Date:     The 15th of June 2005           */
    /* Version:  2.0B                            */
    /*     * ****************************************** */

    public static function ImageCreateFromBMP($filename) {
        // open file in binary mode
        if (!$f1 = fopen($filename, "rb"))
            return FALSE;

        //1 : get headers
        $FILE = unpack("vfile_type/Vfile_size/Vreserved/Vbitmap_offset", fread($f1, 14));
        if ($FILE['file_type'] != 19778)
            return FALSE;

        //2 : get contents
        $BMP = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel' .
                        '/Vcompression/Vsize_bitmap/Vhoriz_resolution' .
                        '/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1, 40));
        $BMP['colors'] = pow(2, $BMP['bits_per_pixel']);
        if ($BMP['size_bitmap'] == 0)
            $BMP['size_bitmap'] = $FILE['file_size'] - $FILE['bitmap_offset'];
        $BMP['bytes_per_pixel'] = $BMP['bits_per_pixel'] / 8;
        $BMP['bytes_per_pixel2'] = ceil($BMP['bytes_per_pixel']);
        $BMP['decal'] = ($BMP['width'] * $BMP['bytes_per_pixel'] / 4);
        $BMP['decal'] -= floor($BMP['width'] * $BMP['bytes_per_pixel'] / 4);
        $BMP['decal'] = 4 - (4 * $BMP['decal']);
        if ($BMP['decal'] == 4)
            $BMP['decal'] = 0;

        //3 : get color palette
        $PALETTE = array();
        if ($BMP['colors'] < 16777216) {
            $PALETTE = unpack('V' . $BMP['colors'], fread($f1, $BMP['colors'] * 4));
        }

        //4 : create image
        $IMG = fread($f1, $BMP['size_bitmap']);
        $VIDE = chr(0);

        $res = imagecreatetruecolor($BMP['width'], $BMP['height']);
        $P = 0;
        $Y = $BMP['height'] - 1;
        while ($Y >= 0) {
            $X = 0;
            while ($X < $BMP['width']) {
                if ($BMP['bits_per_pixel'] == 24)
                    $COLOR = unpack("V", substr($IMG, $P, 3) . $VIDE);
                elseif ($BMP['bits_per_pixel'] == 16) {
                    $COLOR = unpack("n", substr($IMG, $P, 2));
                    $COLOR[1] = $PALETTE[$COLOR[1] + 1];
                } elseif ($BMP['bits_per_pixel'] == 8) {
                    $COLOR = unpack("n", $VIDE . substr($IMG, $P, 1));
                    $COLOR[1] = $PALETTE[$COLOR[1] + 1];
                } elseif ($BMP['bits_per_pixel'] == 4) {
                    $COLOR = unpack("n", $VIDE . substr($IMG, floor($P), 1));
                    if (($P * 2) % 2 == 0)
                        $COLOR[1] = ($COLOR[1] >> 4); else
                        $COLOR[1] = ($COLOR[1] & 0x0F);
                    $COLOR[1] = $PALETTE[$COLOR[1] + 1];
                }
                elseif ($BMP['bits_per_pixel'] == 1) {
                    $COLOR = unpack("n", $VIDE . substr($IMG, floor($P), 1));
                    if (($P * 8) % 8 == 0)
                        $COLOR[1] = $COLOR[1] >> 7;
                    elseif (($P * 8) % 8 == 1)
                        $COLOR[1] = ($COLOR[1] & 0x40) >> 6;
                    elseif (($P * 8) % 8 == 2)
                        $COLOR[1] = ($COLOR[1] & 0x20) >> 5;
                    elseif (($P * 8) % 8 == 3)
                        $COLOR[1] = ($COLOR[1] & 0x10) >> 4;
                    elseif (($P * 8) % 8 == 4)
                        $COLOR[1] = ($COLOR[1] & 0x8) >> 3;
                    elseif (($P * 8) % 8 == 5)
                        $COLOR[1] = ($COLOR[1] & 0x4) >> 2;
                    elseif (($P * 8) % 8 == 6)
                        $COLOR[1] = ($COLOR[1] & 0x2) >> 1;
                    elseif (($P * 8) % 8 == 7)
                        $COLOR[1] = ($COLOR[1] & 0x1);
                    $COLOR[1] = $PALETTE[$COLOR[1] + 1];
                }
                else
                    return FALSE;
                imagesetpixel($res, $X, $Y, $COLOR[1]);
                $X++;
                $P += $BMP['bytes_per_pixel'];
            }
            $Y--;
            $P+=$BMP['decal'];
        }

        // close file
        fclose($f1);

        return $res;
    }

This seems to be accepted on php.net as a good function for creating images resources from BMPs.

Comment by doccie:

Right now I'm using this code and that seems to do the trick. Slightly dodgy to automatically assume it's a BMP, but for my current purposes, it will suffice.

Thanks again for the help!

try {
    $file = new fImage($tmp_file, true);
} catch(fException $e) {
    //die($e->getMessage());
    // probably means this is a BMP file
    $res = papUtils::ImageCreateFromBMP($tmp_file);
    imagejpeg($res, $tmp_file);

    try {
        $file = new fImage($tmp_file, true);
    } catch(fException $e) {
        die($e->getMessage());
    }
}

Comment by @wbond:

@doccie

I saw that code too. Unfortunately, as it stands right now I can't use it because it does not have a license attached, which means it is strictly copyright DHKold. Further, dhkold.com no longer seems active, so there is no way to contact the author to ask for permission.

I'll either need to find another implementation that is properly licensed, have someone write a cleanroom implementation (or an implementation based off of an MIT or BSD licensed work), or write it from scratch myself.

feature request for routing functionalities

Ticket #747 by @xjia

I think we need something like the routing functionality in http://www.slimframework.com/

Currently I'm using flourish together with Slim but somewhere of them don't fit well with each other. If there were something like fRouter?, it will be perfect. :-)

ee http://www.slimframework.com/documentation/stable#routes for details of HTTP routing in Slim.

Comment by @mblarsen

This question has come up a few times before. Will usually recommend Moor as a routing companion for flourish https://github.com/jeffturcotte/moor

Maybe that is a better fit for your needs than Slim's router.

Comment by @xjia

Aha! Yeah you are right. Moor seems good. Thanks.

Create fUnitOfWork

Ticket #539 by @wbond:

There should be an fUnitOfWork class to bundling up ORM operations into a single transaction

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.