_binary From e137e247d368e14d7080fe6b23d605fece340da2 Mon Sep 17 00:00:00 2001
From: diesal9 <charles.bocage@sjsu.edu>
Date: Tue, 10 May 2016 19:32:33 -0700
Subject: [PATCH] I have added the code changes to support the CMS activity.

---
 src/configs/PublicHelpPages.php                    | 224 ++++++-----
 src/controllers/AdminController.php                |  10 +-
 src/controllers/Controller.php                     |  26 +-
 src/controllers/components/CrawlComponent.php      | 209 ++++++++--
 src/css/search.css                                 |  16 +-
 src/library/cms_detectors/UniversalDetector.php    | 191 ++++++++++
 src/library/processors/HtmlProcessor.php           |  45 +--
 src/locale/en_US/configure.ini                     |  53 +++
 src/models/CmsModel.php                            | 156 ++++++++
 src/views/elements/CmsDetectorsElement.php         | 176 +++++++++
 tests/CmsDetectorTest.php                          |   6 +-
 tests/ManyDetectorsExperiment.php                  | 422 +++++++++++++++++++++
 tests/test_files/cms_detector/Joomla01.txt         | 167 ++++++++
 tests/test_files/cms_detector/SiteCore01.txt       | 302 +++++++++++++++
 tests/test_files/cms_detector/SiteCore02.txt       |  47 +++
 tests/test_files/cms_detector/Yioop01.txt          |  62 +++
 .../test_files/cms_detector/cms_detector_input.txt |   8 +-
 .../cms_detector/cms_detector_results.txt          |   6 +
 tests/test_files/cms_detector/vBulletin01.txt      |  77 ++++
 tests/test_files/cms_detector/vBulletin02.txt      |  90 +++++
 20 files changed, 2099 insertions(+), 194 deletions(-)
 create mode 100644 src/library/cms_detectors/UniversalDetector.php
 create mode 100644 src/models/CmsModel.php
 create mode 100644 src/views/elements/CmsDetectorsElement.php
 create mode 100644 tests/ManyDetectorsExperiment.php
 create mode 100644 tests/test_files/cms_detector/Joomla01.txt
 create mode 100644 tests/test_files/cms_detector/SiteCore01.txt
 create mode 100644 tests/test_files/cms_detector/SiteCore02.txt
 create mode 100644 tests/test_files/cms_detector/Yioop01.txt
 create mode 100644 tests/test_files/cms_detector/vBulletin01.txt
 create mode 100644 tests/test_files/cms_detector/vBulletin02.txt

diff --git a/src/configs/PublicHelpPages.php b/src/configs/PublicHelpPages.php
index a832077..50fbfb7 100644
--- a/src/configs/PublicHelpPages.php
+++ b/src/configs/PublicHelpPages.php
@@ -408,7 +408,7 @@ link in the gray box below the textarea. This would add wiki code such as
 &lt;/pre&gt;
 
 to the page. Only saving the page will save this code and upload the resource to
-the server. In the above myphoto.jpg is the resource that will be inserted and 
+the server. In the above myphoto.jpg is the resource that will be inserted and
 Resource Description is the alternative text to use in case the viewing browser
 cannot display jpg files. A list of media that have already been associated with
 a page appears under the Page Resource heading below the textarea. This
@@ -418,7 +418,7 @@ from a different wiki page belonging to the same group to the current wiki
 page one can use a syntax like:
 
 &lt;pre&gt;
-( (resource:Documentation:ConfigureScreenForm1.png|The work directory form)) 
+( (resource:Documentation:ConfigureScreenForm1.png|The work directory form))
 &lt;/pre&gt;
 
 Here Documentation would be the page and ConfigureScreenForm1.png the resource.
@@ -435,10 +435,10 @@ fields or areas for the page title, author, meta robots, and page description.
 Beneath this one can specify another wiki page to be used as a header for this
 page and also specify another wiki page to be used as a footer for this page.
 
-The contents of the page title is displayed in the browser title when the 
-wiki page is accessed with the  Activity Panel collapsed or when not logged in. 
-Similarly, in the collapsed or not logged in mode, if one looks as the HTML 
-page source for the page,  in the head of document, &lt;meta&gt; tags for author, 
+The contents of the page title is displayed in the browser title when the
+wiki page is accessed with the  Activity Panel collapsed or when not logged in.
+Similarly, in the collapsed or not logged in mode, if one looks as the HTML
+page source for the page,  in the head of document, &lt;meta&gt; tags for author,
 robots, and description are set according to these fields. These fields can
 be useful for search engine optimization. The robots meta tag can be
 used to control how search engine robots index the page. Wikipedia has more information on
@@ -453,17 +453,17 @@ you were on the wiki page dental_floss when you switch to French the article
 dental_floss might redirect to the page dentrifice.
 
 &#039;&#039;&#039;Media List&#039;&#039;&#039; type means that the page, when read, should display just the
-resources in the page as a list of thumbnails and links. These links for the 
-resources go to a separate pages used to display these resources. 
+resources in the page as a list of thumbnails and links. These links for the
+resources go to a separate pages used to display these resources.
 This kind of page is useful for a gallery of
-images or a collection of audio or video files. 
+images or a collection of audio or video files.
 
 &#039;&#039;&#039;Presentation&#039;&#039;&#039; type is for a wiki page whose purpose is a slide presentation. In this mode,
 ....
 on a line by itself is used to separate one slide. If presentation type is a selected a new
-slide icon appears in the wiki edit bar allowining one to easily add new slides. 
-When the Activity panel is not collapsed and you are reading a presentation, it just 
-displays as a single page with all slides visible. Collapsing the Activity panel presents 
+slide icon appears in the wiki edit bar allowining one to easily add new slides.
+When the Activity panel is not collapsed and you are reading a presentation, it just
+displays as a single page with all slides visible. Collapsing the Activity panel presents
 the slides as a typical slide presentation using the
 [[www.w3.org/Talks/Tools/Slidy2/Overview.html|Slidy]] javascript.
 EOD;
@@ -555,17 +555,17 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARS=Title=
-* Slide Item
-* Slide Item
-* Slide Item
-....
-=Title=
-* Slide Item
-* Slide Item
-* Slide Item
-....
-
+END_HEAD_VARS=Title=
+* Slide Item
+* Slide Item
+* Slide Item
+....
+=Title=
+* Slide Item
+* Slide Item
+* Slide Item
+....
+
 
 EOD;
 $public_pages["en-US"]["privacy"] = <<< 'EOD'
@@ -668,7 +668,7 @@ description=Help article describing how to add a Locale.
 
 END_HEAD_VARS==Adding a Locale==
 
-The Manage Locales activity can be used to configure Yioop for use with 
+The Manage Locales activity can be used to configure Yioop for use with
 different languages and for different regions.
 
 * The first form on this activity allows you to create a new &amp;quot;Locale&amp;quot;
@@ -831,6 +831,33 @@ You will then be presented with a web form to narrow your search followed by
 a list of all visible groups to you beneath.
 {{right|[[https://www.seekquarry.com/?c=static&amp;p=Documentation#Managing%20Users,%20Roles,%20and%20Groups| Learn More..]]}}
 EOD;
+$help_pages["en-US"]["CMS_Detectors"] = <<< EOD
+page_type=standard
+
+page_alias=
+
+page_border=solid-border
+
+toc=true
+
+title=
+
+author=
+
+robots=
+
+description=
+
+alternative_path=
+
+page_header=
+
+page_footer=
+
+sort=aname
+
+END_HEAD_VARS&#039;&#039;&#039;CMS Detectors&#039;&#039;&#039; are used to help Yioop get to the most important content on a web page. &lt;br /&gt;&lt;br /&gt; You must enter the &#039;&#039;&#039;Name&#039;&#039;&#039;. The Header Regex and Important Content XPath are optional but will have no effect if they are not entered. &lt;br /&gt; &#039;&#039;&#039;The Header Regex&#039;&#039;&#039; is used to detect the CMS. The header of most CMS created sites are very common. A specifically crafted regular expression can be used to detect the CMS you are looking for. It looks in the href value in a rel=&#039;stylesheet&#039; tag or the src value in a type=&#039;text/javascript&#039; tag. &lt;br /&gt;&lt;br /&gt; The &#039;&#039;&#039;Important Content XPath&#039;&#039;&#039; is used to target the most important content for summarizing. The first entry is where to target the important content. Any subsequent entry will be used to remove content within the important content. Append each removal XPath to the end of the value delimited by three pound signs (###). &lt;br /&gt; &#039;&#039;&#039;Example:&#039;&#039;&#039; &lt;br /&gt;&lt;br /&gt; &lt;table border=&#039;1&#039;&gt; &lt;th&gt;Setting&lt;/th&gt; &lt;th&gt;Value&lt;/th&gt; &lt;tr&gt;&lt;td&gt;Name&lt;/td&gt;&lt;td&gt;Wordpress&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Header Regex &lt;/td&gt;&lt;td&gt;wp-(?:content|includes)&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;Important Content XPath&lt;/td&gt;&lt;td&gt;//div[@id=&quot;content&quot;]###&lt;br /&gt;//div[@id=&quot;comments&quot;]###&lt;br /&gt;//div[@id=&quot;respond&quot;]&lt;/td&gt;&lt;/tr&gt; &lt;/table&gt; &lt;br /&gt;
+EOD;
 $help_pages["en-US"]["Captcha_Type"] = <<< EOD
 page_type=standard
 
@@ -854,21 +881,21 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARSThe Captcha Type field set controls what kind of
-[[https://en.wikipedia.org/wiki/CAPTCHA|captcha]] will be used during account
-registration, password recovery, and if a user wants to suggest a url. The choices for captcha are:
-* &#039;&#039;&#039;Text Captcha&#039;&#039;&#039;, the user has to select from a series of dropdown answers
-to questions of the form: &#039;&#039;Which in the following list is the most/largest/etc?
-or Which is the following list is the least/smallest/etc?; &#039;&#039;
-* &#039;&#039;&#039;Graphic Captcha&#039;&#039;&#039;, the user needs to enter a sequence of characters from
-a distorted image;
-* &#039;&#039;&#039;Hash captcha&#039;&#039;&#039;, the user&#039;s browser (the user doesn&#039;t need to do anything)
-needs to extend a random string with additional characters to get a string
-whose hash begins with a certain lead set of characters.
-
-Of these, Hash Captcha is probably the least intrusive but requires
-Javascript and might run slowly on older browsers. A text captcha might be used
-to test domain expertise of the people who are registering for an account.
+END_HEAD_VARSThe Captcha Type field set controls what kind of
+[[https://en.wikipedia.org/wiki/CAPTCHA|captcha]] will be used during account
+registration, password recovery, and if a user wants to suggest a url. The choices for captcha are:
+* &#039;&#039;&#039;Text Captcha&#039;&#039;&#039;, the user has to select from a series of dropdown answers
+to questions of the form: &#039;&#039;Which in the following list is the most/largest/etc?
+or Which is the following list is the least/smallest/etc?; &#039;&#039;
+* &#039;&#039;&#039;Graphic Captcha&#039;&#039;&#039;, the user needs to enter a sequence of characters from
+a distorted image;
+* &#039;&#039;&#039;Hash captcha&#039;&#039;&#039;, the user&#039;s browser (the user doesn&#039;t need to do anything)
+needs to extend a random string with additional characters to get a string
+whose hash begins with a certain lead set of characters.
+
+Of these, Hash Captcha is probably the least intrusive but requires
+Javascript and might run slowly on older browsers. A text captcha might be used
+to test domain expertise of the people who are registering for an account.
 Finally, the graphic captcha is probably the one people are most familiar with.
 EOD;
 $help_pages["en-US"]["Changing_the_Classifier_Label"] = <<< EOD
@@ -1372,7 +1399,7 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARS&#039;&#039;&#039;Machine Information&#039;&#039;&#039; shows the currently known about machines. 
+END_HEAD_VARS&#039;&#039;&#039;Machine Information&#039;&#039;&#039; shows the currently known about machines.
 
 &lt;br /&gt;
 
@@ -1407,23 +1434,23 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARSThe &#039;&#039;&#039;Advertisement Name&#039;&#039;&#039;, &#039;&#039;&#039;Text Description&#039;&#039;&#039;, &#039;&#039;&#039;Destination URL&#039;&#039;&#039; fields can be used to create a text-based advertisement. What this ad will look like appears in the &#039;&#039;&#039;Preview&#039;&#039;&#039; area.
-&lt;br /&gt;
-
-The &#039;&#039;&#039;Duration&#039;&#039;&#039; dropdown controls how many days the ad campaign will run for. The campaign starts on the date of purchase and this first day till midnight Pacific Time counts as one day of duration.
-&lt;br /&gt;
-
-&#039;&#039;&#039;Keywords&#039;&#039;&#039; should consist of a comma separated list of words or phrases. Each word or phrase has a minimum bid for each day based on demand for that keyword. If no one so far has purchased an ad for any of the keywords, then this minimum is $1/day/word or phrase. Otherwise, it is calculated using the total of the bids so far.
-&lt;br /&gt; 
-
-The &#039;&#039;&#039;Calculate Bid&#039;&#039;&#039; button computes the minimum cost for the campaign you have chosen, add presents a form to receive your credit card information.  
-
-On this form the static field &#039;&#039;&#039;Minimum Bid Required&#039;&#039;&#039; field gives the minimum amount required to pay for the advertisement campaign in question. The &#039;&#039;&#039;Expensive word&#039;&#039;&#039; static field says for your campaign which term contributes the most to this minimum bid cost. The Budget fields allows you to enter an amount greater than or equal to the minimum bid that you are willing to pay your ad campaign. If there have been no other bids on your keywords then the minimum bid will show you ad 100% of the time any of your keywords are search for. If, however, there have been other bids, your bid amount as a fraction of the total bid amount for that day for the search keyword is used to select a frequency with which your ad is displayed, so it can make sense to bid more than the minimum required amount.
-&lt;br /&gt;
-
-If you need to edit the keywords or other details of your ad before purchasing it, you can click the &#039;&#039;&#039;Edit Ad&#039;&#039;&#039; button; otherwise, clicking the &#039;&#039;&#039;Purchase&#039;&#039;&#039; button completes the purchase of your Ad campaign.
-&lt;br /&gt;
-
+END_HEAD_VARSThe &#039;&#039;&#039;Advertisement Name&#039;&#039;&#039;, &#039;&#039;&#039;Text Description&#039;&#039;&#039;, &#039;&#039;&#039;Destination URL&#039;&#039;&#039; fields can be used to create a text-based advertisement. What this ad will look like appears in the &#039;&#039;&#039;Preview&#039;&#039;&#039; area.
+&lt;br /&gt;
+
+The &#039;&#039;&#039;Duration&#039;&#039;&#039; dropdown controls how many days the ad campaign will run for. The campaign starts on the date of purchase and this first day till midnight Pacific Time counts as one day of duration.
+&lt;br /&gt;
+
+&#039;&#039;&#039;Keywords&#039;&#039;&#039; should consist of a comma separated list of words or phrases. Each word or phrase has a minimum bid for each day based on demand for that keyword. If no one so far has purchased an ad for any of the keywords, then this minimum is $1/day/word or phrase. Otherwise, it is calculated using the total of the bids so far.
+&lt;br /&gt;
+
+The &#039;&#039;&#039;Calculate Bid&#039;&#039;&#039; button computes the minimum cost for the campaign you have chosen, add presents a form to receive your credit card information.
+
+On this form the static field &#039;&#039;&#039;Minimum Bid Required&#039;&#039;&#039; field gives the minimum amount required to pay for the advertisement campaign in question. The &#039;&#039;&#039;Expensive word&#039;&#039;&#039; static field says for your campaign which term contributes the most to this minimum bid cost. The Budget fields allows you to enter an amount greater than or equal to the minimum bid that you are willing to pay your ad campaign. If there have been no other bids on your keywords then the minimum bid will show you ad 100% of the time any of your keywords are search for. If, however, there have been other bids, your bid amount as a fraction of the total bid amount for that day for the search keyword is used to select a frequency with which your ad is displayed, so it can make sense to bid more than the minimum required amount.
+&lt;br /&gt;
+
+If you need to edit the keywords or other details of your ad before purchasing it, you can click the &#039;&#039;&#039;Edit Ad&#039;&#039;&#039; button; otherwise, clicking the &#039;&#039;&#039;Purchase&#039;&#039;&#039; button completes the purchase of your Ad campaign.
+&lt;br /&gt;
+
 The &#039;&#039;&#039;Advertisement List&#039;&#039;&#039; beneath the form lists details for all of the ads you have created from most recent to least recent as well as impression and click information. You can edit the text of your ad (but not the keywords) by clicking an ad&#039;s edit column. You can also Deactivate a campaign to stop it from displaying. This does not refund your money.
 EOD;
 $help_pages["en-US"]["Manage_Credits"] = <<< EOD
@@ -1447,24 +1474,24 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARS&#039;&#039;&#039;Purchase Ad Credits&#039;&#039;&#039; form can be used to purchase ad credits which can then be spent under &#039;&#039;&#039;Manage Advertisements&#039;&#039;&#039;.
-&lt;br /&gt;&lt;br /&gt;
-
-The &#039;&#039;&#039;Quantity&#039;&#039;&#039; dropdown specifies the number of credits one wants to purchase at what price.
-&lt;br /&gt;
-
-The &#039;&#039;&#039;Card Number&#039;&#039;&#039; field should be filled in with a valid credit card.
-&lt;br /&gt;
-
-The &#039;&#039;&#039;CVC&#039;&#039;&#039; field you should put the three or four digit card verification number for your card.
-&lt;br /&gt;
-
-The &#039;&#039;&#039;Expiration&#039;&#039;&#039; dropdown is used to set your cards expiration date.
-&lt;br /&gt; 
-
-The &#039;&#039;&#039;Purchase&#039;&#039;&#039; button is used to complete the purchase of Ad credit.
-&lt;br /&gt;
-
+END_HEAD_VARS&#039;&#039;&#039;Purchase Ad Credits&#039;&#039;&#039; form can be used to purchase ad credits which can then be spent under &#039;&#039;&#039;Manage Advertisements&#039;&#039;&#039;.
+&lt;br /&gt;&lt;br /&gt;
+
+The &#039;&#039;&#039;Quantity&#039;&#039;&#039; dropdown specifies the number of credits one wants to purchase at what price.
+&lt;br /&gt;
+
+The &#039;&#039;&#039;Card Number&#039;&#039;&#039; field should be filled in with a valid credit card.
+&lt;br /&gt;
+
+The &#039;&#039;&#039;CVC&#039;&#039;&#039; field you should put the three or four digit card verification number for your card.
+&lt;br /&gt;
+
+The &#039;&#039;&#039;Expiration&#039;&#039;&#039; dropdown is used to set your cards expiration date.
+&lt;br /&gt;
+
+The &#039;&#039;&#039;Purchase&#039;&#039;&#039; button is used to complete the purchase of Ad credit.
+&lt;br /&gt;
+
 Beneath the Purchase form is the list of &#039;&#039;&#039;Ad Credit Transactions&#039;&#039;&#039; that have been made with your account.
 EOD;
 $help_pages["en-US"]["Manage_Machines"] = <<< EOD
@@ -1488,7 +1515,7 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARS&#039;&#039;&#039;Add Machine&#039;&#039;&#039; allows you to add a new machine to be controlled by this Yioop instance. 
+END_HEAD_VARS&#039;&#039;&#039;Add Machine&#039;&#039;&#039; allows you to add a new machine to be controlled by this Yioop instance.
 
 &lt;br /&gt;
 
@@ -1496,7 +1523,7 @@ The &#039;&#039;&#039;Machine Name&#039;&#039;&#039; field lets you give this ma
 
 &lt;br /&gt;
 
-The &#039;&#039;&#039;Mirror&#039;&#039;&#039; check-box says whether you want the given Yioop installation to act as a mirror for another Yioop installation. Checking it will reveal a drop-down menu that allows you to choose which installation amongst the previously entered machines you want to mirror. 
+The &#039;&#039;&#039;Mirror&#039;&#039;&#039; check-box says whether you want the given Yioop installation to act as a mirror for another Yioop installation. Checking it will reveal a drop-down menu that allows you to choose which installation amongst the previously entered machines you want to mirror.
 
 &lt;br /&gt;
 
@@ -1531,34 +1558,34 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARS&#039;&#039;&#039;Media Sources&#039;&#039;&#039; are used to specify how Yioop should handle video and news sites. 
+END_HEAD_VARS&#039;&#039;&#039;Media Sources&#039;&#039;&#039; are used to specify how Yioop should handle video and news sites.
 
 &lt;br /&gt;
 
 A &#039;&#039;&#039;Video source&#039;&#039;&#039; is used to specify where to find the thumb nail of a video given the url of the video on a website. This is used by Yioop when displaying search results containing the video link to show the thumb nail. For example, if the Url value is
- http://www.youtube.com/watch?v={} 
+ http://www.youtube.com/watch?v={}
 and the Thumb value is
- http://i1.ytimg.com/vi/{}/default.jpg, 
+ http://i1.ytimg.com/vi/{}/default.jpg,
 this tells Yioop that if a search result contains something like
 &lt;pre&gt;
  https://www.youtube.com/watch?v=dQw4w9WgXcQ
 &lt;/pre&gt;
 this says find the thumb at
 &lt;pre&gt;
- http://i1.ytimg.com/vi/dQw4w9WgXcQ/default.jpg 
+ http://i1.ytimg.com/vi/dQw4w9WgXcQ/default.jpg
 &lt;/pre&gt;
 
-An &#039;&#039;&#039;RSS media source&#039;&#039;&#039; can be used to add an RSS or Atom feed (it auto-detects which kind) to the list of feeds which are downloaded hourly when Yioop&#039;s Media Updater is turned on. Besides the name you need to specify the URL of the feed in question. 
+An &#039;&#039;&#039;RSS media source&#039;&#039;&#039; can be used to add an RSS or Atom feed (it auto-detects which kind) to the list of feeds which are downloaded hourly when Yioop&#039;s Media Updater is turned on. Besides the name you need to specify the URL of the feed in question.
 
 &lt;br /&gt;
 
 An &#039;&#039;&#039;HTML media source&#039;&#039;&#039; is a web page that has news articles like an RSS page that you want the Media Updater to scrape on an hourly basis. To specify where in the HTML page the news items appear you specify different XPath information. For example,
 &lt;pre&gt;
- Name: Cape Breton Post	
+ Name: Cape Breton Post
  URL: http://www.capebretonpost.com/News/Local-1968
- Channel: //div[contains(@class, &quot;channel&quot;)]	
+ Channel: //div[contains(@class, &quot;channel&quot;)]
  Item: //article
- Title:	//a
+ Title:    //a
  Description: //div[contains(@class, &quot;dek&quot;)]
  Link: //a
 &lt;/pre&gt;
@@ -1598,7 +1625,7 @@ END_HEAD_VARSYioop can be run in a single machine or multi-machine setting. In a
 
 &lt;br /&gt;
 
-When someone enters a query into a Yioop set-up, they typically enter the query on the name server. The &#039;&#039;&#039;Use Filecache&#039;&#039;&#039; checkbox controls whether the query results are cached in a file so that they don&#039;t have to be recalculated when someone enters the same query again. The file cache is purged periodically so that it doesn&#039;t get too large.  
+When someone enters a query into a Yioop set-up, they typically enter the query on the name server. The &#039;&#039;&#039;Use Filecache&#039;&#039;&#039; checkbox controls whether the query results are cached in a file so that they don&#039;t have to be recalculated when someone enters the same query again. The file cache is purged periodically so that it doesn&#039;t get too large.
 EOD;
 $help_pages["en-US"]["Page_Byte_Ranges"] = <<< EOD
 page_type=standard
@@ -1671,16 +1698,16 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARSThe &#039;&#039;&#039;Search Results Grouping&#039;&#039;&#039; controls allow you to control on a search query how many qualifying documents from an index to compute before trying to sort and rank them to find the top k results (here k is usually 10).  In a multi-queue-server setting the query is simultaneously asked by the name server machine of each of the queue server machines and the results are aggregated. 
+END_HEAD_VARSThe &#039;&#039;&#039;Search Results Grouping&#039;&#039;&#039; controls allow you to control on a search query how many qualifying documents from an index to compute before trying to sort and rank them to find the top k results (here k is usually 10).  In a multi-queue-server setting the query is simultaneously asked by the name server machine of each of the queue server machines and the results are aggregated.
 
 &lt;br /&gt;
 
 &#039;&#039;&#039;Minimum Results to Group&#039;&#039;&#039; controls the number of results the name server want to have before sorting of results is done. When the name server request documents from each queue server, it requests for
 &lt;br /&gt;
-&amp;alpha; &amp;times; (Minimum Results to Group)/(Number of Queue Servers) documents. 
+&amp;alpha; &amp;times; (Minimum Results to Group)/(Number of Queue Servers) documents.
 
 &lt;br /&gt;
-&#039;&#039;&#039;Server Alpha&#039;&#039;&#039; controls the number alpha. 
+&#039;&#039;&#039;Server Alpha&#039;&#039;&#039; controls the number alpha.
 EOD;
 $help_pages["en-US"]["Page_Ranking_Factors"] = <<< EOD
 page_type=standard
@@ -1780,10 +1807,10 @@ page_header=
 
 page_footer=
 
-END_HEAD_VARSThe Recovery Type field set controls whether and how user account recovery can be handled in an automated fashion. The available choices are:
-* &#039;&#039;&#039;No User Password Recovery Link&#039;&#039;&#039;, no &quot;Forgot Password?&quot; link is provided and a user cannot recover
-their password on their own;
-* &#039;&#039;&#039;Email Link Password Recovery&#039;&#039;&#039;, a user can specify their login and get emailed a password change link;
+END_HEAD_VARSThe Recovery Type field set controls whether and how user account recovery can be handled in an automated fashion. The available choices are:
+* &#039;&#039;&#039;No User Password Recovery Link&#039;&#039;&#039;, no &quot;Forgot Password?&quot; link is provided and a user cannot recover
+their password on their own;
+* &#039;&#039;&#039;Email Link Password Recovery&#039;&#039;&#039;, a user can specify their login and get emailed a password change link;
 * &#039;&#039;&#039;Email Link and Check Questions Recovery&#039;&#039;&#039;, a user can specify their login and get emailed a password change link. The password change page requires the user correctly answers previously provided recovery questions.
 EOD;
 $help_pages["en-US"]["Search_Results_Editor"] = <<< EOD
@@ -1838,7 +1865,7 @@ pages appear or not.
 ; &#039;&#039;&#039;Signin&#039;&#039;&#039; : Controls whether the &#039;&#039;&#039;Sign In&#039;&#039;&#039; link appears at the top of the Yioop landing and search result pages.
 ; &#039;&#039;&#039;Cache&#039;&#039;&#039;, &#039;&#039;&#039;Similar&#039;&#039;&#039;, &#039;&#039;&#039;Inlinks&#039;&#039;&#039;, &#039;&#039;&#039;IP Address&#039;&#039;&#039;: Control whether the corresponding links appear after each search result item.
 
-	
+
 
 EOD;
 $help_pages["en-US"]["Seed_Sites_and_URL_Suggestions"] = <<< EOD
@@ -1937,13 +1964,13 @@ END_HEAD_VARS&#039;&#039;&#039;Subsearches&#039;&#039;&#039; are specialized sea
 
 &lt;br /&gt;
 
-A list of links to all the current subsearches on a Yioop site appears at the 
+A list of links to all the current subsearches on a Yioop site appears at the
  site_url?a=more
 page. Links to some of the subsearches may appear at the top left hand side of of the default landing page provided the Pages Options : Search Time : Subsearch checkbox is checked.
 
 &lt;br /&gt;
 
-The &#039;&#039;&#039;Folder Name&#039;&#039;&#039; of a subsearch is the name that appears as part of the query string when doing a search restricted to that subsearch. After creating a subsearch, the table below will have a &#039;&#039;&#039;Localize&#039;&#039;&#039; link next to its name. This lets you give names for your subsearch on the More page mentioned above with respect to different languages. 
+The &#039;&#039;&#039;Folder Name&#039;&#039;&#039; of a subsearch is the name that appears as part of the query string when doing a search restricted to that subsearch. After creating a subsearch, the table below will have a &#039;&#039;&#039;Localize&#039;&#039;&#039; link next to its name. This lets you give names for your subsearch on the More page mentioned above with respect to different languages.
 
 EOD;
 $help_pages["en-US"]["Summary_Length"] = <<< EOD
@@ -2056,10 +2083,10 @@ page_header=
 page_footer=
 
 END_HEAD_VARSThe &#039;&#039;&#039;Work Directory&#039;&#039;&#039; is a folder used to store all the customizations of this instance of Yioop.
-This field should be a complete file system path to a folder that exists. 
+This field should be a complete file system path to a folder that exists.
 It should use forward slashes. For example:
 
- /some_folder/some_subfolder/yioop_data 
+ /some_folder/some_subfolder/yioop_data
 (more appropriate for Mac or Linux) or
  c:/some_folder/some_subfolder/yioop_data
 (more appropriate on a Windows system).
@@ -2068,4 +2095,3 @@ If you decide to upgrade Yioop at some later date you only have to replace the c
 of Yioop and set the Work Directory path to the value of your pre-upgrade version. For this
 reason the Work Directory should not be a subfolder of the Yioop code folder.
 EOD;
-
diff --git a/src/controllers/AdminController.php b/src/controllers/AdminController.php
index c356f72..94037d9 100755
--- a/src/controllers/AdminController.php
+++ b/src/controllers/AdminController.php
@@ -72,7 +72,7 @@ class AdminController extends Controller implements CrawlConstants
         "accountaccess" =>
             ["signin", "manageAccount", "manageUsers", "manageRoles"],
         "crawl" => ["manageCrawls", "manageClassifiers", "pageOptions",
-            "resultsEditor", "searchSources"],
+            "resultsEditor", "searchSources", "cmsDetectors"],
         "social" => ["manageGroups", "groupFeeds", "mixCrawls", "wiki"],
         "advertisement" => ["manageCredits", "manageAdvertisements"],
         "system" => ["manageMachines", "manageLocales", "serverSettings",
@@ -341,7 +341,7 @@ class AdminController extends Controller implements CrawlConstants
             }
         }
         // business role only allows managing advertisements;
-        if(!$allowed && $activity == "manageAccount") {
+        if (!$allowed && $activity == "manageAccount") {
             $activity = $allowed_activities[0]['METHOD_NAME'];
             $_REQUEST["a"] = $activity;
             $allowed = true;
@@ -380,7 +380,7 @@ class AdminController extends Controller implements CrawlConstants
             "advertisement" => tl('admin_controller_advertisement')
         ];
         if (isset($data["ACTIVITIES"])) {
-            foreach (self::$component_activities as $component => $activities) {
+            foreach (self::$component_activities as $component => $activities){
                 foreach ($data["ACTIVITIES"] as $activity) {
                     if (in_array($activity['METHOD_NAME'], $activities)) {
                         $data['COMPONENT_ACTIVITIES'][
@@ -510,7 +510,7 @@ class AdminController extends Controller implements CrawlConstants
                 }
                 $data[$field] = $clean_value;
                 $profile[$field] = $data[$field];
-                if ($field == "MEMCACHE_SERVERS" || $field == "PROXY_SERVERS") {
+                if ($field == "MEMCACHE_SERVERS" || $field == "PROXY_SERVERS"){
                     $mem_array = preg_split("/(\s)+/", $clean_value);
                     $profile[$field] =
                         $this->convertArrayLines(
@@ -610,4 +610,4 @@ class AdminController extends Controller implements CrawlConstants
         $data['PAGING'] = $paging;
         return $search_array;
     }
-}
+}
\ No newline at end of file
diff --git a/src/controllers/Controller.php b/src/controllers/Controller.php
index 4a4b15a..9b8fd28 100755
--- a/src/controllers/Controller.php
+++ b/src/controllers/Controller.php
@@ -157,8 +157,10 @@ abstract class Controller
     public function component($component)
     {
         if (!isset($this->component_instances[$component])) {
-            $component_name = C\NS_COMPONENTS . ucfirst($component)."Component";
-            $this->component_instances[$component] = new $component_name($this);
+            $component_name = C\NS_COMPONENTS . ucfirst($component) .
+                "Component";
+            $this->component_instances[$component] =
+                new $component_name($this);
         }
         return $this->component_instances[$component];
     }
@@ -200,7 +202,7 @@ abstract class Controller
     public function getIndexingPluginList()
     {
         $dir_prefixes = [C\BASE_DIR, C\APP_DIR];
-        foreach($dir_prefixes as $dir_prefix) {
+        foreach ($dir_prefixes as $dir_prefix) {
             $plugin_dir = $dir_prefix."/library/indexing_plugins/";
             $plugin_dir_len = strlen($plugin_dir);
             $plugin_ext_len = strlen("Plugin.php");
@@ -334,7 +336,7 @@ abstract class Controller
         } else {
             unset($_SESSION['DISPLAY_MESSAGE']);
         }
-        if(php_sapi_name() == 'cli') {
+        if (php_sapi_name() == 'cli') {
             //this case happens for configure_tool.php
             return $data;
         }
@@ -602,11 +604,11 @@ abstract class Controller
     public function clean($value, $type, $default = null)
     {
         $clean_value = null;
-        if(is_array($type)) {
-            if(in_array($value, $type)) {
+        if (is_array($type)) {
+            if (in_array($value, $type)) {
                 return $value;
             } else {
-                if($default != null) {
+                if ($default != null) {
                     return $default;
                 }
                 reset($type);
@@ -736,6 +738,8 @@ abstract class Controller
             case "string":
                 if (isset($value)) {
                     $value2 = str_replace("&amp;", "&", $value);
+                    // -CP REMEMBER TO CK THIS!!!!!
+                    $value2 = mb_convert_encoding($value2, "UTF-8");
                     $clean_value = @htmlentities($value2, ENT_QUOTES, "UTF-8");
                 } else {
                     $clean_value = $default;
@@ -750,7 +754,7 @@ abstract class Controller
                         $value = "http://$value";
                         $is_web = true;
                     }
-                    if($is_web) {
+                    if ($is_web) {
                         $value = str_replace("&amp;", "&", $value);
                         $clean_value = @htmlentities($value, ENT_QUOTES,
                             "UTF-8");
@@ -853,7 +857,7 @@ abstract class Controller
      */
     public function parsePageHeadVarsView($view, $page_name, $page_data)
     {
-        list($view->head_objects[$page_name], $view->page_objects[$page_name]) =
+        list($view->head_objects[$page_name], $view->page_objects[$page_name])=
             $this->parsePageHeadVars($page_data, true);
     }
     /**
@@ -870,7 +874,7 @@ abstract class Controller
      *      static page
      * @param bool whether to output just an array of head variables or
      *      if output a pair [head vars, page body]
-     * @return array the associative array of head variables or pair 
+     * @return array the associative array of head variables or pair
      *      [head vars, page body]
      */
     public function parsePageHeadVars($page_data, $with_body = false)
@@ -920,4 +924,4 @@ abstract class Controller
             }
         }
      }
-}
+}
\ No newline at end of file
diff --git a/src/controllers/components/CrawlComponent.php b/src/controllers/components/CrawlComponent.php
index 5c60f6d..fcf09f3 100644
--- a/src/controllers/components/CrawlComponent.php
+++ b/src/controllers/components/CrawlComponent.php
@@ -41,8 +41,8 @@ use seekquarry\yioop\library\UrlParser;
 use seekquarry\yioop\library\processors\PageProcessor;
 
 /**
- * This component is used to provide activities for the admin controller related
- * to configuring and performing a web or archive crawl
+ * This component is used to provide activities for the admin controller
+ * related to configuring and performing a web or archive crawl
  *
  * @author Chris Pollett
  */
@@ -56,8 +56,8 @@ class CrawlComponent extends Component implements CrawlConstants
      * restart an old crawl. It also allows a user to configure the options
      * by which a crawl is conducted
      *
-     * @return array $data information and statistics about crawls in the system
-     *     as well as status messages on performing a given sub activity
+     * @return array $data information and statistics about crawls in the
+     *     system as well as status messages on performing a given sub activity
      */
     public function manageCrawls()
     {
@@ -137,7 +137,8 @@ class CrawlComponent extends Component implements CrawlConstants
                 case "delete":
                     if (isset($_REQUEST['timestamp'])) {
                          $timestamp = substr($parent->clean(
-                            $_REQUEST['timestamp'], "int"), 0, C\TIMESTAMP_LEN);
+                            $_REQUEST['timestamp'], "int"), 0,
+                            C\TIMESTAMP_LEN);
                          $crawl_model->deleteCrawl($timestamp,
                             $machine_urls);
                         return $parent->redirectWithMessage(
@@ -240,7 +241,8 @@ class CrawlComponent extends Component implements CrawlConstants
         $crawl_params[self::CRAWL_INDEX] =
             (isset($seed_info['general']['crawl_index'])) ?
             $seed_info['general']['crawl_index'] : '';
-        $crawl_params[self::ARC_DIR]=(isset($seed_info['general']['arc_dir'])) ?
+        $crawl_params[self::ARC_DIR]=
+            (isset($seed_info['general']['arc_dir'])) ?
             $seed_info['general']['arc_dir'] : '';
         $crawl_params[self::ARC_TYPE] =
             (isset($seed_info['general']['arc_type'])) ?
@@ -262,7 +264,8 @@ class CrawlComponent extends Component implements CrawlConstants
             intval($seed_info['general']['page_recrawl_frequency']) :
             C\PAGE_RECRAWL_FREQUENCY;
         $crawl_params[self::TO_CRAWL] = $seed_info['seed_sites']['url'];
-        $crawl_params[self::CRAWL_ORDER] = $seed_info['general']['crawl_order'];
+        $crawl_params[self::CRAWL_ORDER] =
+            $seed_info['general']['crawl_order'];
         $crawl_params[self::RESTRICT_SITES_BY_URL] =
             $seed_info['general']['restrict_sites_by_url'];
         $crawl_params[self::ALLOWED_SITES] =
@@ -462,9 +465,11 @@ class CrawlComponent extends Component implements CrawlConstants
         }
         $data['crawl_index'] =  (isset($seed_info['general']['crawl_index'])) ?
             $seed_info['general']['crawl_index'] : '';
-        $data['available_crawl_types'] = [self::WEB_CRAWL, self::ARCHIVE_CRAWL];
+        $data['available_crawl_types'] =
+            [self::WEB_CRAWL, self::ARCHIVE_CRAWL];
         if (!$no_further_changes && isset($_REQUEST['crawl_type']) &&
-            in_array($_REQUEST['crawl_type'], $data['available_crawl_types'])) {
+            in_array($_REQUEST['crawl_type'],
+            $data['available_crawl_types'])) {
             $seed_info['general']['crawl_type'] = $_REQUEST['crawl_type'];
             $update_flag = true;
         }
@@ -564,7 +569,8 @@ class CrawlComponent extends Component implements CrawlConstants
                 $crawl_model->setSeedInfo($seed_info);
             }
             return $parent->redirectWithMessage(
-                tl('crawl_component_update_seed_info')." $add_message",["arg"]);
+                tl('crawl_component_update_seed_info'). " $add_message",
+                ["arg"]);
         }
         return $data;
     }
@@ -705,6 +711,7 @@ class CrawlComponent extends Component implements CrawlConstants
         if ($data['reload'] && !$start_finalizing) {
             $data['SCRIPT'] .= "doMessage('<h1 class=\"red\">".
                 tl('crawl_component_finalizing_classifier'). '</h1>\');';
+
         }
         return $data;
     }
@@ -850,7 +857,7 @@ class CrawlComponent extends Component implements CrawlConstants
             self::CENTROID_SUMMARIZER =>  tl('crawl_component_centroid'),
             self::CENTROID_WEIGHTED_SUMMARIZER =>
                 tl('crawl_component_centroid_weighted'),
-            self::GRAPH_BASED_SUMMARIZER =>  tl('crawl_component_graph_based')];
+            self::GRAPH_BASED_SUMMARIZER => tl('crawl_component_graph_based')];
         if (!isset($seed_info["indexed_file_types"]["extensions"])) {
             $seed_info["indexed_file_types"]["extensions"] =
                 PageProcessor::$indexed_file_types;
@@ -900,7 +907,8 @@ class CrawlComponent extends Component implements CrawlConstants
                     $_REQUEST["page_recrawl_frequency"];
             }
             if (isset($_REQUEST["page_range_request"]) &&
-                in_array($_REQUEST["page_range_request"],$data['SIZE_VALUES'])){
+                in_array($_REQUEST["page_range_request"],
+                $data['SIZE_VALUES'])) {
                 $seed_info["general"]["page_range_request"] =
                     $_REQUEST["page_range_request"];
             }
@@ -911,7 +919,8 @@ class CrawlComponent extends Component implements CrawlConstants
                     $_REQUEST['summarizer_option'];
             }
             if (isset($_REQUEST["max_description_len"]) &&
-                in_array($_REQUEST["max_description_len"],$data['LEN_VALUES'])){
+                in_array($_REQUEST["max_description_len"],
+                $data['LEN_VALUES'])) {
                 $seed_info["general"]["max_description_len"] =
                     $_REQUEST["max_description_len"];
             }
@@ -934,7 +943,8 @@ class CrawlComponent extends Component implements CrawlConstants
         }
         $data['summarizer_option'] = isset(
             $seed_info['general']['summarizer_option']) ?
-            $seed_info['general']['summarizer_option'] : self::BASIC_SUMMARIZER;
+            $seed_info['general']['summarizer_option'] :
+            self::BASIC_SUMMARIZER;
         $data['PAGE_RECRAWL_FREQUENCY'] =
             $seed_info["general"]["page_recrawl_frequency"];
         if (!isset($seed_info["general"]["cache_pages"])) {
@@ -968,9 +978,9 @@ class CrawlComponent extends Component implements CrawlConstants
             $data['INDEXING_PLUGINS'][$plugin_name]['checked'] =
                 (in_array($plugin_name, $included_plugins)) ?
                 "checked='checked'" : "";
-            /* to use method_exists we want that the require_once for the plugin
-               class has occurred so we instantiate the object via the plugin
-               method call which will also do the require if needed.
+            /* to use method_exists we want that the require_once for the
+               plugin class has occurred so we instantiate the object via the
+               plugin method call which will also do the require if needed.
              */
             $plugin_object = $parent->plugin(lcfirst($plugin_name));
             $class_name = C\NS_PLUGINS . $plugin_name."Plugin";
@@ -1195,7 +1205,7 @@ class CrawlComponent extends Component implements CrawlConstants
                 "Processor";
             $plugin_processors = [];
             if (isset($seed_info['indexing_plugins']['plugins'])) {
-                foreach ($seed_info['indexing_plugins']['plugins'] as $plugin) {
+                foreach ($seed_info['indexing_plugins']['plugins'] as $plugin){
                     if ($plugin == "") { continue; }
                     $plugin_name = C\NS_PLUGINS . $plugin."Plugin";
                     $tmp_object = new $plugin_name();
@@ -1320,6 +1330,135 @@ class CrawlComponent extends Component implements CrawlConstants
         return $data;
     }
     /**
+     * Handles admin request related to the CMS Detectors activity
+     *
+     * This activity allows a user to specify the configuration for the
+     * ways we detect CMSs
+     *
+     * @return array $data info about the CMS detectors settings
+     */
+    public function cmsDetectors()
+    {
+        $parent = $this->parent;
+        $crawl_model = $parent->model("crawl");
+        $source_model = $parent->model("cms");
+        $possible_arguments = ["addsource", "deletesource",
+            "editsource"];
+        $request_fields = ['start_row', 'num_show', 'end_row'];
+        $data = [];
+        $data["ELEMENT"] = "cmsDetectors";
+        $data['SCRIPT'] = "";
+        if (isset($_SESSION['USER_ID'])) {
+            $user = $_SESSION['USER_ID'];
+        } else {
+            $user = $_SERVER['REMOTE_ADDR'];
+        }
+        $data['CMS_DETECTORS_SETTINGS'] =
+            $source_model->getsCmsDetectorsSettings($user);
+        $n = C\NUM_RESULTS_PER_PAGE;
+        $data['PER_PAGE'] = [$n => $n, 2*$n => 2*$n, 5*$n=> 5*$n,
+            10*$n=>10*$n];
+        if (isset($_REQUEST['per_page']) &&
+            in_array($_REQUEST['per_page'], array_keys($data['PER_PAGE']))) {
+            $data['PER_PAGE_SELECTED'] = $_REQUEST['per_page'];
+        } else {
+            $data['PER_PAGE_SELECTED'] = C\NUM_RESULTS_PER_PAGE;
+        }
+        $locales = $parent->model("locale")->getLocaleList();
+        $data["LANGUAGES"] = [];
+        foreach ($locales as $locale) {
+            $data["LANGUAGES"][$locale['LOCALE_TAG']] = $locale['LOCALE_NAME'];
+        }
+        if (isset($_REQUEST['language']) &&
+            in_array($_REQUEST['language'],
+                array_keys($data["LANGUAGES"]))) {
+            $data['SOURCE_LOCALE_TAG'] =
+                $_REQUEST['language'];
+        } else {
+            $data['SOURCE_LOCALE_TAG'] = C\DEFAULT_LOCALE;
+        }
+        $data["CURRENT_CMS_DETECTORS"] = [
+            "name" => "", "header" => "",
+            "important_content" => ""];
+        $data['SOURCE_FORM_TYPE'] = "addsource";
+        if (isset($_REQUEST['arg']) &&
+            in_array($_REQUEST['arg'], $possible_arguments)) {
+            switch ($_REQUEST['arg'])
+            {
+                case "addsource":
+                    if (! $_REQUEST['name']) {
+                        return $parent->redirectWithMessage(
+                            tl('crawl_component_cmsdetectors_no_name'),
+                            $request_fields);
+                    }
+                    $source_model->addCmsDetectors(
+                        $_REQUEST['name'], $_REQUEST['header'],
+                        $_REQUEST['important_content']);
+                    return $parent->redirectWithMessage(
+                        tl('crawl_component_cmsdetectors_added'),
+                        $request_fields);
+                break;
+                case "deletesource":
+                    if (!isset($_REQUEST['ts'])) {
+                        return $parent->redirectWithMessage(
+                            tl('crawl_component_no_delete_cmsdetectors'),
+                            $request_fields);
+                    }
+                    $timestamp = $parent->clean($_REQUEST['ts'], "string");
+                    $source_model->deleteCmsDetectors($timestamp);
+                    return $parent->redirectWithMessage(
+                        tl('crawl_component_media_cmsdetectors_deleted'),
+                        $request_fields);
+                break;
+                case "editsource":
+                    $data['SOURCE_FORM_TYPE'] = "editsource";
+                    $source = false;
+                    $timestamp = (isset($_REQUEST['ts'])) ?
+                        $parent->clean($_REQUEST['ts'], "string") : "";
+                    if ($timestamp) {
+                        $source = $source_model->getCmsDetectors($timestamp);
+                    }
+                    if (!$source) {
+                        $data['SOURCE_FORM_TYPE'] = "addsource";
+                        break;
+                    }
+                    $data['ts'] = $timestamp;
+                    $update = false;
+                    foreach ($data['CURRENT_CMS_DETECTORS']
+                        as $field => $value) {
+                        $upper_field = strtoupper($field);
+                        if (isset($_REQUEST[$field]) && $field != 'name') {
+                            $source[$upper_field] = $parent->clean(
+                                $_REQUEST[$field], "string");
+                            $data['CURRENT_CMS_DETECTORS'][$field] =
+                                $source[$upper_field];
+                            $update = true;
+                        } else if (isset($source[$upper_field])){
+                            $data['CURRENT_CMS_DETECTORS'][$field] =
+                                $source[$upper_field];
+                        }
+                    }
+                    if ($update) {
+                        $source_model->updateCmsDetectors($source);
+                        $fields = array_merge(array("arg", "ts"),
+                            $request_fields);
+                        return $parent->redirectWithMessage(
+                            tl('crawl_component_cmsdetectors_updated'),
+                            $fields);
+                    }
+                break;
+            }
+        }
+        $data['CAN_LOCALIZE'] = $parent->model("user")->isAllowedUserActivity(
+            $_SESSION['USER_ID'], "manageLocales");
+        $parent->pagingLogic($data, $source_model, "CMS_DETETCTORS",
+            C\DEFAULT_ADMIN_PAGING_NUM/5, [["NAME", "", "", "ASC"]]);
+        $data['SCRIPT'] .= "source_type = elt('source-type');".
+            "source_type.onchange = switchSourceType;".
+            "switchSourceType()";
+        return $data;
+    }
+    /**
      * Handles admin request related to the search filter activity
      *
      * This activity allows a user to specify hosts whose web pages are to be
@@ -1380,7 +1519,8 @@ class CrawlComponent extends Component implements CrawlConstants
                             ["URL", "TITLE", "DESCRIPTION"]);
                     } else {
                         $filters_model->updateResultPage(
-                            $data["URL"], $data["TITLE"], $data["DESCRIPTION"]);
+                            $data["URL"], $data["TITLE"],
+                            $data["DESCRIPTION"]);
                         return $parent->redirectWithMessage(
                             tl('crawl_component_results_editor_page_updated'),
                             ["URL", "TITLE", "DESCRIPTION"]);
@@ -1389,9 +1529,10 @@ class CrawlComponent extends Component implements CrawlConstants
                 case "load_url":
                     $hash_url = L\crawlHash($_REQUEST['LOAD_URL'], true);
                     if (isset($summaries[$hash_url])) {
-                        $_REQUEST["URL"] = $parent->clean($_REQUEST['LOAD_URL'],
-                            "web-url");
-                        $_REQUEST["TITLE"] = $summaries[$hash_url][self::TITLE];
+                        $_REQUEST["URL"] =
+                            $parent->clean($_REQUEST['LOAD_URL'], "web-url");
+                        $_REQUEST["TITLE"] =
+                            $summaries[$hash_url][self::TITLE];
                         $_REQUEST["DESCRIPTION"] = $summaries[$hash_url][
                             self::DESCRIPTION];
                         return $parent->redirectWithMessage(
@@ -1460,7 +1601,8 @@ class CrawlComponent extends Component implements CrawlConstants
                 $item["NAME"];
         }
         $n = C\NUM_RESULTS_PER_PAGE;
-        $data['PER_PAGE'] = [$n => $n, 2*$n => 2*$n, 5*$n=> 5*$n, 10*$n=>10*$n];
+        $data['PER_PAGE'] = [$n => $n, 2*$n => 2*$n, 5*$n=> 5*$n,
+            10*$n=>10*$n];
         if (isset($_REQUEST['per_page']) &&
             in_array($_REQUEST['per_page'], array_keys($data['PER_PAGE']))) {
             $data['PER_PAGE_SELECTED'] = $_REQUEST['per_page'];
@@ -1520,8 +1662,8 @@ class CrawlComponent extends Component implements CrawlConstants
                         ['aux_info','language', 'image_xpath']);
                     foreach ($to_clean as $clean_me) {
                         $r[$clean_me] = (isset($_REQUEST[$clean_me])) ?
-                            trim($parent->clean($_REQUEST[$clean_me], "string"))
-                            : "";
+                            trim($parent->clean($_REQUEST[$clean_me],
+                            "string"))  : "";
                         if ($clean_me == "source_url") {
                             $r[$clean_me] = UrlParser::canonicalLink(
                                 $r[$clean_me], "");
@@ -1559,8 +1701,8 @@ class CrawlComponent extends Component implements CrawlConstants
                     $must_have = $to_clean;
                     foreach ($to_clean as $clean_me) {
                         $r[$clean_me] = (isset($_REQUEST[$clean_me])) ?
-                            trim($parent->clean($_REQUEST[$clean_me],"string")):
-                            "";
+                            trim($parent->clean($_REQUEST[$clean_me],
+                            "string")) : "";
                         if (in_array($clean_me, $must_have) &&
                             ($r[$clean_me] == "" || $r[$clean_me] == -1)) {
                             return $parent->redirectWithMessage(
@@ -1658,8 +1800,8 @@ class CrawlComponent extends Component implements CrawlConstants
                         $aux_parts = explode("###", $source['AUX_INFO']);
                         list($source['CHANNEL_PATH'],
                             $source['ITEM_PATH'], $source['TITLE_PATH'],
-                            $source['DESCRIPTION_PATH'], $source['LINK_PATH']) =
-                                $aux_parts;
+                            $source['DESCRIPTION_PATH'],
+                            $source['LINK_PATH']) = $aux_parts;
                         if (isset($aux_parts[5])) {
                             $source['IMAGE_XPATH'] = $aux_parts[5];
                         } else {
@@ -1689,7 +1831,8 @@ class CrawlComponent extends Component implements CrawlConstants
                     }
                     if ($update) {
                         if ($is_html_feed) {
-                            $source['AUX_INFO'] = $source['CHANNEL_PATH']."###".
+                            $source['AUX_INFO'] =
+                                $source['CHANNEL_PATH']."###" .
                             $source['ITEM_PATH']."###".
                             $source['TITLE_PATH'] . "###" .
                             $source['DESCRIPTION_PATH'] . "###".
@@ -1723,8 +1866,8 @@ class CrawlComponent extends Component implements CrawlConstants
             ["FOLDER_NAME", "", "", "ASC"]], "SUB",
             "SUBSEARCH");
         foreach ($data["SUBSEARCHES"] as $search) {
-            if (!isset($data["SEARCH_LISTS"][trim($search['INDEX_IDENTIFIER'])])
-                ) {
+            if (!isset($data["SEARCH_LISTS"]
+                [trim($search['INDEX_IDENTIFIER'])])) {
                 $source_model->deleteSubsearch($search["FOLDER_NAME"]);
             }
         }
@@ -1733,4 +1876,4 @@ class CrawlComponent extends Component implements CrawlConstants
             "switchSourceType()";
         return $data;
     }
-}
+}
\ No newline at end of file
diff --git a/src/css/search.css b/src/css/search.css
index b55cc63..5d90bc1 100755
--- a/src/css/search.css
+++ b/src/css/search.css
@@ -2405,6 +2405,20 @@ div.group-result
     border: 1px ridge black;
     padding: 2px;
 }
+.cms-detectors-table
+{
+    border: 1px ridge black;
+    padding: 2px;
+    width: 100%;
+    table-layout: fixed;
+}
+.cms-detectors-table td,
+.cms-detectors-table th
+{
+    border: 1px ridge black;
+    padding: 2px;
+    word-wrap: break-word;
+}
 .mixes-table td
 {
     font-size:12pt;
@@ -3026,4 +3040,4 @@ p.start-ad
     font-family: Arial, Helvetica, sans-serif;
     min-height: 0.5in;
     width: 3in;
-}
+}
\ No newline at end of file
diff --git a/src/library/cms_detectors/UniversalDetector.php b/src/library/cms_detectors/UniversalDetector.php
new file mode 100644
index 0000000..b9b6045
--- /dev/null
+++ b/src/library/cms_detectors/UniversalDetector.php
@@ -0,0 +1,191 @@
+<?php
+/**
+ * SeekQuarry/Yioop --
+ * Open Source Pure PHP Search Engine, Crawler, and Indexer
+ *
+ * Copyright (C) 2009 - 2016  Chris Pollett chris@pollett.org
+ *
+ * LICENSE:
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * END LICENSE
+ *
+ * @author Charles Bocage (charles.bocage@sjsu.edu)
+ * @license http://www.gnu.org/licenses/ GPL3
+ * @link http://www.seekquarry.com/
+ * @copyright 2009 - 2016
+ * @filesource
+ */
+namespace seekquarry\yioop\library\cms_detectors;
+
+use seekquarry\yioop\models as M;
+/**
+ *
+ */
+class UniversalDetector extends CmsDetector
+{
+    /**
+     * The important content XPath to use for finding the important content
+     */
+    public $important_content_xpath = "";
+    /**
+     * The header regex to use for searching the page's header
+     */
+    public $header_Regex = "";
+    /**
+     * The name of the CMS found is one is found
+     */
+    public $cms_name = "";
+    /**
+     * The head tags found in the page
+     */
+    public $head_tags = "";
+    /**
+     * The constructor for the class
+     *
+     * @param string $page the html page to check
+     * @return string True if it finds a match, otherwise false
+     */
+    public function __construct($page)
+    {
+        $this->head_tags = $this->getHeadTags($page);
+    }
+    /**
+     * Method used to check for a CMS match or not.  It uses what is in the
+     * head tags to determine if there is a match or not.
+     *
+     * @param string $page the html page to check
+     * @return string True if it finds a match, otherwise false
+     */
+    public function check($page)
+    {
+        $result = false;
+        $detectorSettings = $this->getSystems();
+        foreach ($detectorSettings as $detectorSetting) {
+            $this->header_Regex =
+                html_entity_decode($detectorSetting['HEADER'],
+                ENT_QUOTES, 'UTF-8');
+            if ($this->checkHeadTags()) {
+                $this->timestamp =
+                    html_entity_decode($detectorSetting['TIMESTAMP'],
+                    ENT_QUOTES, 'UTF-8');
+                $this->important_content_xpath =
+                    html_entity_decode($detectorSetting['IMPORTANT_CONTENT'],
+                    ENT_QUOTES, 'UTF-8');
+                $this->cms_name = $detectorSetting['NAME'];
+                $result = true;
+                break;
+            }
+        }
+        return $result;
+    }
+    /**
+     * Return the name of the CMS it is used to detect.
+     *
+     * @return string The name of the CMS it is used to detect.
+     */
+    public function getName()
+    {
+        return $this->cms_name;
+    }
+    /**
+     * Return the name of the CMS it is used to detect.
+     *
+     * @return string The name of the CMS it is used to detect.
+     */
+    public function frameworkName()
+    {
+        return "UNIVERSAL";
+    }
+    /**
+     * Return the important section of the document.  Say the content
+     * is stored in a div tag with id of content, it should return that
+     * content from this method. It also strips out irrelavent tags that are
+     * sometimes contained in the subsection.
+     *
+     * @param string $page the html page to check
+     * @return string The important section of the document.
+     */
+    public function getImportantContent($page)
+    {
+        $important_content_xpaths = preg_split('/###/u',
+            $this->important_content_xpath, 0, PREG_SPLIT_NO_EMPTY);
+        if (count($important_content_xpaths) > 0) {
+            $important_content_xpath = $important_content_xpaths[0];
+            unset($important_content_xpaths[0]);
+            $temp_page = $this->getContentByXquery($page,
+                $important_content_xpath);
+            if (!empty($temp_page)) {
+                foreach ($important_content_xpaths as $tag_to_remove) {
+                    $temp_content_to_remove =
+                        $this->getContentByXquery($page, $tag_to_remove);
+                    $temp_page =
+                        str_replace($temp_content_to_remove, "", $temp_page);
+                }
+            }
+        }
+        return empty($temp_page) ? $page : $temp_page;
+    }
+    /**
+     * Get the framework detectors from the database.
+     *
+     * @return array The array of detectors found in the database.
+     */
+    public function getSystems()
+    {
+        $result = [];
+        $cmsModel = new M\CmsModel();
+        $result = $cmsModel->getsCmsDetectorsSettings();
+        return $result;
+    }
+    /**
+     * Check the header tags to see if we find a rel tag that is a stylesheet
+     * and has the CMS content in its value.  There is code to check
+     * that it has scripts in there too but not all Wordpress sites have them.
+     *
+     * @return boolean True if there is more than one stylesheet found,
+     *      otherwise false.
+     */
+    private function checkHeadTags()
+    {
+        $matches = 0;
+        $rel_tag_match = 0;
+        $rel_tag_match_minimum = 0;
+        $script_tag_match = 0;
+        $script_tag_match_minimum = 0;
+        $head_tags_regex = "/" . $this->header_Regex . "/iu";
+        if ($this->head_tags) {
+            foreach ($this->head_tags as $head_tag) {
+                if (@$relType = $head_tag['attr']['rel']) {
+                    if (@$attributeSubValue = $head_tag['attr']['href']) {
+                        $tempResult = $this->isTagMatch($relType,
+                            "stylesheet", $head_tags_regex,
+                            $attributeSubValue) ? 1 : 0;
+                        $rel_tag_match = $rel_tag_match + $tempResult;
+                    }
+                } elseif (@$scriptType = $head_tag['attr']['type']) {
+                    if (@$attributeSubValue = $head_tag['attr']['src']) {
+                        $tempResult = $this->isTagMatch($scriptType,
+                            "text/javascript", $head_tags_regex,
+                            $attributeSubValue) ? 1 : 0;
+                        $script_tag_match = $script_tag_match + $tempResult;
+                    }
+                }
+            }
+        }
+        return $rel_tag_match > $rel_tag_match_minimum ||
+            $script_tag_match > $script_tag_match_minimum;
+    }
+}
\ No newline at end of file
diff --git a/src/library/processors/HtmlProcessor.php b/src/library/processors/HtmlProcessor.php
index c7e915a..520abdd 100755
--- a/src/library/processors/HtmlProcessor.php
+++ b/src/library/processors/HtmlProcessor.php
@@ -33,7 +33,7 @@ namespace seekquarry\yioop\library\processors;
 use seekquarry\yioop\configs as C;
 use seekquarry\yioop\library as L;
 use seekquarry\yioop\library\UrlParser;
-
+use seekquarry\yioop\library\cms_detectors\UniversalDetector;
 /**
  * Used to create crawl summary information
  * for HTML files
@@ -76,28 +76,6 @@ class HtmlProcessor extends TextProcessor
         self::$mime_processor["text/html"] = "HtmlProcessor";
         self::$mime_processor["text/asp"] = "HtmlProcessor";
         self::$mime_processor["application/xhtml+xml"] = "HtmlProcessor";
-        $this->cms_detector_list = $this->getCmsDetectorList();
-    }
-    /**
-     * Get the framework detectors that are in this folder.
-     *
-     * @return array The array of detectors found in this folder.
-     */
-    public function getCmsDetectorList()
-    {
-        $detectors = [];
-        $detector_prefixes = [C\BASE_DIR, C\APP_DIR];
-        foreach ($detector_prefixes as $detector_prefix) {
-            $detector_dir = $detector_prefix . "/library/cms_detectors/";
-            foreach (glob("$detector_dir*Detector.php") as $filename) {
-                $detector_name = lcfirst(substr($filename,
-                    strlen($detector_dir), -strlen("Detector.php")));
-                if (!in_array($detector_name, ['cms'])) {
-                    $detectors[] = $detector_name;
-                }
-            }
-        }
-        return $detectors;
     }
     /**
      * @param string $page the html page to check
@@ -107,28 +85,13 @@ class HtmlProcessor extends TextProcessor
      */
     public function checkForCMSContent($page)
     {
-        foreach ($this->cms_detector_list as $cms_system) {
-            $cms_detector = $this->cmsDetector($cms_system);
-            if (!empty($cms_detector) && $cms_detector->check($page)) {
+        $cms_detector = new UniversalDetector($page);
+            if ($cms_detector->check($page)) {
                 return $cms_detector;
             }
-        }
         return null;
     }
     /**
-     *
-     */
-    public function cmsDetector($name)
-    {
-        static $detector_instances = [];
-        if (!empty($detector_instances[$name])) {
-            return $detector_instances[$name];
-        }
-        $detector_name = C\NS_DETECTORS . ucfirst($name)."Detector";
-        $detector_instances[$name] = new $detector_name();
-        return $detector_instances[$name];
-    }
-    /**
      * Used to extract the title, description and links from
      * a string consisting of webpage data.
      *
@@ -634,4 +597,4 @@ class HtmlProcessor extends TextProcessor
         $text = preg_replace('/\</', ' <', $text);
         return strip_tags($text);
     }
-}
+}
\ No newline at end of file
diff --git a/src/locale/en_US/configure.ini b/src/locale/en_US/configure.ini
index d01a7ac..3ba647c 100644
--- a/src/locale/en_US/configure.ini
+++ b/src/locale/en_US/configure.ini
@@ -4839,3 +4839,56 @@ wordfilter_plugin_factory_settings = "Factory Settings"
 ;
 ; WordfilterPlugin.php line: 485
 wordfilter_plugin_save = "Save"
+;
+; CmsDetectorsElement.php line: 67
+cmsdetectors_element_add_cmd_detectors = "Add CMS Detector"
+; CmsDetectorsElement.php line: 90
+cmsdetectors_element_sourcename = "Name:"
+;
+; CmsDetectorsElement.php line: 99
+cmsdetectors_element_header = "Header Regex:"
+;
+; CmsDetectorsElement.php line: 105
+cmsdetectors_element_importantcontent = "Important Content XPath:"
+;
+; CmsDetectorsElement.php line: 112
+cmsdetectors_element_submit = "Submit"
+;
+; CmsDetectorsElement.php line: 118
+cmsdetectors_element_cms_Detectors = "CMS Detectors"
+;
+; CmsDetectorsElement.php line: 142
+cmsdetectors_element_detectorsname = "Name"
+;
+; CmsDetectorsElement.php line: 143
+cmsdetectors_element_detectorsheader = "Header Regex"
+;
+; CmsDetectorsElement.php line: 145
+cmsdetectors_element_detectorsimportantcontent = "Important Content XPath"
+;
+; CmsDetectorsElement.php line: 148
+cmsdetectors_element_detectorsaction = "Actions"
+;
+; CmsDetectorsElement.php line: 62
+cmsdetectors_element_addcmsdetectors_form = "Add CMS Detector Form"
+;
+; CmsDetectorsElement.php line: 63
+cmsdetectors_element_edit_cms_detectors = "Edit CMS Detector"
+;
+; CmsDetectorsElement.php line: 166
+cmsdetectors_element_deletecmsdetectors = "Delete"
+;
+; CrawlComponent.php line: 1392
+crawl_component_cmsdetectors_added = "CMS Detector Added!"
+;
+; CrawlComponent.php line: 1397
+crawl_component_no_delete_cmsdetectors = "CMS Detector Was Not Deleted!"
+;
+; CrawlComponent.php line: 1403
+crawl_component_media_cmsdetectors_deleted = "CMS Detector Deleted!"
+;
+; CrawlComponent.php line: 1386
+crawl_component_cmsdetectors_no_name = "Name Not Set!"
+;
+; CrawlComponent.php line: 1441
+crawl_component_cmsdetectors_updated = "CMS Detector Updated!"
\ No newline at end of file
diff --git a/src/models/CmsModel.php b/src/models/CmsModel.php
new file mode 100644
index 0000000..5ad16a5
--- /dev/null
+++ b/src/models/CmsModel.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ * SeekQuarry/Yioop --
+ * Open Source Pure PHP Search Engine, Crawler, and Indexer
+ *
+ * Copyright (C) 2009 - 2016  Chris Pollett chris@pollett.org
+ *
+ * LICENSE:
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * END LICENSE
+ *
+ * @author Chris Pollett chris@pollett.orgs
+ * @license http://www.gnu.org/licenses/ GPL3
+ * @link http://www.seekquarry.com/
+ * @copyright 2009 - 2016
+ * @filesource
+ */
+namespace seekquarry\yioop\models;
+
+use seekquarry\yioop\configs as C;
+use seekquarry\yioop\library as L;
+use seekquarry\yioop\library\CrawlConstants;
+use seekquarry\yioop\library\FetchUrl;
+use seekquarry\yioop\library\IndexShard;
+use seekquarry\yioop\library\PhraseParser;
+use seekquarry\yioop\library\UrlParser;
+use seekquarry\yioop\controllers\AdminController;
+
+/** For getLocaleTag*/
+require_once __DIR__.'/../library/LocaleFunctions.php';
+/**
+ * Used to manage data related to CMS_DETECTORS
+ *
+ * @author Charles Bocage
+ */
+class CmsModel extends ParallelModel
+{
+    /**
+     * Controls which tables and the names of tables
+     * underlie the given model and should be used in a getRows call
+     *
+     * @param string $args it does not matter.
+     * @return string which table to use
+     */
+    public function fromCallback($args = null)
+    {
+        return "CMS_DETECTORS";
+    }
+    /**
+     * Return the contents of the CMS_DETECTORS table
+     * @return array associative array with TIMESTAMP, NAME, HEADER,
+     *     and LANGUAGE
+     */
+    public function getsCmsDetectorsSettings()
+    {
+        $db = $this->db;
+        $sources = [];
+        $sql = "SELECT * FROM CMS_DETECTORS";
+        $result = $db->execute($sql);
+        if (!$result) {
+            return false;
+        }
+        $i = 0;
+        while ($sources[$i] = $db->fetchArray($result)) {
+            $i++;
+        }
+        unset($sources[$i]); //last one will be null
+        return $sources;
+    }
+    /**
+     * Used to add a new video, rss, html news, or other sources to Yioop
+     *
+     * @param string $name
+     * @param string $source_header the regex to query the html headers for
+     *     a match
+     * @param string $source_importantcontent the xpath strings used to find
+     *     the important content in the html document
+     */
+    public function addCmsDetectors($name, $source_header,
+        $source_importantcontent)
+    {
+        $admin = new AdminController();
+        $db = $this->db;
+        $sql = "INSERT INTO CMS_DETECTORS VALUES (?,?,?,?)";
+        $name = $admin->clean($name, "string");
+        $source_header = $admin->clean($source_header, "string");
+        $source_importantcontent = $admin->clean($source_importantcontent,
+            "string");
+        $db->execute($sql, [time(), $name, $source_header,
+            $source_importantcontent]);
+    }
+    /**
+     * Deletes the cms detector whose id is the given timestamp
+     *
+     * @param int $timestamp of media source to be deleted
+     */
+    public function deleteCmsDetectors($timestamp)
+    {
+        $sql = "DELETE FROM CMS_DETECTORS WHERE TIMESTAMP=?";
+        $this->db->execute($sql, [$timestamp]);
+    }
+    /**
+     * Return the cms detector by the timestamp of the source
+     * @param string $timestamp of the cms detectors to look up
+     * @return array associative array with TIMESTAMP, NAME, HEADER and
+     *     IMPORTANT_CONTENT
+     */
+    public function getCmsDetectors($timestamp)
+    {
+        $db = $this->db;
+        $sql = "SELECT * FROM CMS_DETECTORS WHERE TIMESTAMP = ?";
+        $result = $db->execute($sql, [$timestamp]);
+        if (!$result) {
+            return false;
+        }
+        $row = $db->fetchArray($result);
+        return $row;
+    }
+    /**
+     * Used to update the fields stored in a CMS_DETECTORS row according to
+     * an array holding new values
+     * @param array $source_info updated values for a CMS_DETECTORS row
+     */
+    public function updateCmsDetectors($source_info)
+    {
+        $timestamp = $source_info['TIMESTAMP'];
+        unset($source_info['TIMESTAMP']);
+        unset($source_info['NAME']);
+        $sql = "UPDATE CMS_DETECTORS SET ";
+        $comma ="";
+        $params = [];
+        $admin = new AdminController();
+        foreach ($source_info as $field => $value) {
+            $sql .= "$comma $field=? ";
+            $comma = ",";
+            //$value = $admin->clean($value, "string");
+            $params[] = $value;
+        }
+        $sql .= " WHERE TIMESTAMP=?";
+        $params[] = $timestamp;
+        $this->db->execute($sql, $params);
+    }
+}
\ No newline at end of file
diff --git a/src/views/elements/CmsDetectorsElement.php b/src/views/elements/CmsDetectorsElement.php
new file mode 100644
index 0000000..d59e0f6
--- /dev/null
+++ b/src/views/elements/CmsDetectorsElement.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * SeekQuarry/Yioop --
+ * Open Source Pure PHP Search Engine, Crawler, and Indexer
+ *
+ * Copyright (C) 2009 - 2016  Chris Pollett chris@pollett.org
+ *
+ * LICENSE:
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * END LICENSE
+ *
+ * @author Chris Pollett chris@pollett.org
+ * @license http://www.gnu.org/licenses/ GPL3
+ * @link http://www.seekquarry.com/
+ * @copyright 2009 - 2016
+ * @filesource
+ */
+namespace seekquarry\yioop\views\elements;
+
+use seekquarry\yioop as B;
+use seekquarry\yioop\configs as C;
+
+/**
+ * Contains the forms for managing CMS Detection.
+ *
+ * @author Charles Bocage
+ */
+class CmsDetectorsElement extends Element
+{
+    /**
+     * Renders search source and subsearch forms
+     *
+     * @param array $data available Search sources  and subsearches
+     */
+    public function render($data)
+    {
+        $admin_url = htmlentities(B\controllerUrl('admin', true));
+        $token_string = C\CSRF_TOKEN."=".$data[C\CSRF_TOKEN];
+        $pre_base_url = $admin_url . $token_string;
+        $base_url = $pre_base_url . "&amp;a=cmsDetectors";
+        $localize_url = $pre_base_url . "&amp;a=manageLocales".
+            "&amp;arg=editstrings&amp;selectlocale=".$data['LOCALE_TAG'].
+            "&amp;previous_activity=cmsDetectors";
+        ?>
+        <div class="current-activity">
+        <?php if ($data["SOURCE_FORM_TYPE"] == "editsource") {
+            ?>
+            <div class='float-opposite'><a href='<?= $base_url ?>'><?=
+                tl('cmsdetectors_element_addcmsdetectors_form') ?></a></div>
+            <h2><?= tl('cmsdetectors_element_edit_cms_detectors')?></h2>
+            <?php
+        } else {
+            ?>
+            <h2><?= tl('cmsdetectors_element_add_cmd_detectors')?>
+            <?= $this->view->helper("helpbutton")->render(
+                "CMS Detectors", $data[C\CSRF_TOKEN]) ?>
+            </h2>
+            <?php
+        }
+        ?>
+        <form id="addCmsDetectorsForm" method="post">
+        <input type="hidden" name="c" value="admin" />
+        <input type="hidden" name="<?= C\CSRF_TOKEN ?>" value="<?=
+            $data[C\CSRF_TOKEN] ?>" />
+        <input type="hidden" name="a" value="cmsDetectors" />
+        <input type="hidden" name="arg" value="<?=
+            $data['SOURCE_FORM_TYPE']?>" />
+        <?php
+        if ($data['SOURCE_FORM_TYPE'] == "editsource") {
+            ?>
+            <input type="hidden" name="ts" value="<?= $data['ts']?>" />
+            <?php
+        }
+        ?>
+        <table class="name-table">
+        <tr><td><label for="source-name"><b><?=
+            tl('cmsdetectors_element_sourcename')?></b></label></td><td>
+            <input type="text" id="source-name" name="name"
+                value="<?=$data['CURRENT_CMS_DETECTORS']['name'] ?>"
+                maxlength="<?= C\LONG_NAME_LEN ?>"
+                class="wide-field" <?php
+                if ($data["SOURCE_FORM_TYPE"] == "editsource") {
+                    e("disabled='disabled'");
+                } ?>/></td></tr>
+        <tr><td><label for="source-header"><b><?=
+            tl('cmsdetectors_element_header')?></b></label></td><td>
+            <input type="header" id="source-header" name="header"
+                value="<?=$data['CURRENT_CMS_DETECTORS']['header'] ?>"
+                maxlength="<?=C\MAX_URL_LEN ?>"
+                class="wide-field" /></td></tr>
+        <tr><td><label for="source-important-content"><b><?=
+            tl('cmsdetectors_element_importantcontent')?></b></label></td><td>
+            <input type="importantcontent" id="source-importantcontent"
+                name="important_content"
+                value=
+                "<?=$data['CURRENT_CMS_DETECTORS']['important_content']?>"
+                maxlength="<?=C\MAX_URL_LEN ?>"
+                class="wide-field" /></td></tr>
+        <tr><td></td><td class="center"><button class="button-box"
+            type="submit"><?=tl('cmsdetectors_element_submit')
+            ?></button></td></tr>
+        </table>
+        </form>
+        <?php
+        $data['FORM_TYPE'] = "";
+        $data['TABLE_TITLE'] = tl('cmsdetectors_element_cms_Detectors');
+        $data['NO_FLOAT_TABLE'] = true;
+        $data['ACTIVITY'] = 'cmsDetectors';
+        $data['VIEW'] = $this->view;
+        $data['NO_SEARCH'] = true;
+        $paging_items = ['SUBstart_row', 'SUBend_row', 'SUBnum_show'];
+
+        $paging1 = "";
+        foreach ($paging_items as $item) {
+            if (isset($data[strtoupper($item)])) {
+                $paging1 .= "&amp;".$item."=".$data[strtoupper($item)];
+            }
+        }
+        $paging2 = "";
+        $paging_items = ['start_row', 'end_row', 'num_show'];
+        foreach ($paging_items as $item) {
+            if (isset($data[strtoupper($item)])) {
+                $paging2 .= "&amp;".$item."=".$data[strtoupper($item)];
+            }
+        }
+        $data['PAGING'] = $paging1;
+        $this->view->helper("pagingtable")->render($data);
+        ?>
+        <table class="cms-detectors-table">
+        <tr><th><?= tl('cmsdetectors_element_detectorsname') ?></th>
+            <th><?= tl('cmsdetectors_element_detectorsheader') ?></th>
+            <th width="40%"><?=
+                tl('cmsdetectors_element_detectorsimportantcontent') ?></th>
+            <th colspan="2" width="20%"><?=
+                tl('cmsdetectors_element_detectorsaction') ?></th>
+        <?php
+        foreach ($data['CMS_DETECTORS_SETTINGS'] as $source) {
+            $encode_source = urlencode(
+                urlencode($source['NAME']));
+        ?>
+        <tr><td><b><?= $source['NAME'] ?></b></td>
+            <td><?= $source['HEADER'] ?></td>
+            <td>
+                <?= preg_replace("/###/", "###<br />",
+                    $source['IMPORTANT_CONTENT']); ?>
+            </td>
+            <td><a href="<?=$base_url."&amp;arg=editsource&amp;ts=".
+                $source['TIMESTAMP'].$paging1 . $paging2 ?>"><?=
+                tl('searchsources_element_editmedia')
+            ?></a></td>
+            <td><a onclick='javascript:return confirm("<?=
+                tl('confirm_delete_operators') ?>");' href="<?=
+                $base_url."&amp;arg=deletesource&amp;ts=".
+                $source['TIMESTAMP'].$paging1.$paging2 ?>"><?=
+                tl('cmsdetectors_element_deletecmsdetectors')
+            ?></a></td></tr>
+        <?php
+        } ?>
+        </table>
+        </div>
+    <?php
+    }
+}
\ No newline at end of file
diff --git a/tests/CmsDetectorTest.php b/tests/CmsDetectorTest.php
index a33470d..9291213 100644
--- a/tests/CmsDetectorTest.php
+++ b/tests/CmsDetectorTest.php
@@ -85,8 +85,8 @@ class CmsDetectorTest extends UnitTest
             }
             $this->assertEqual($cms_detector_return_value,
                 $return_value_to_compare, "function check ".
-                "correctly detects the contents in \"$file_name\" ".
-                "to $return_value_to_compare");
+                "incorrectly detects the contents in \"$file_name\" ".
+                "[$cms_detector_return_value] to $return_value_to_compare");
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/ManyDetectorsExperiment.php b/tests/ManyDetectorsExperiment.php
new file mode 100644
index 0000000..dded526
--- /dev/null
+++ b/tests/ManyDetectorsExperiment.php
@@ -0,0 +1,422 @@
+<?php
+/**
+ * SeekQuarry/Yioop --
+ * Open Source Pure PHP Search Engine, Crawler, and Indexer
+ *
+ * Copyright (C) 2009 - 2016  Chris Pollett chris@pollett.org
+ *
+ * LICENSE:
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * END LICENSE
+ *
+ * @author Charles Bocage charles.bocage@sjsu.edu
+ * @license http://www.gnu.org/licenses/ GPL3
+ * @link http://www.seekquarry.com/
+ * @copyright 2009 - 2016
+ * @filesource
+ */
+namespace seekquarry\yioop\tests;
+
+use seekquarry\yioop\configs as C;
+use seekquarry\yioop\library as L;
+use seekquarry\yioop\models as M;
+
+if (isset($_SERVER['DOCUMENT_ROOT']) && strlen($_SERVER['DOCUMENT_ROOT']) > 0){
+    echo "BAD REQUEST";
+    exit();
+}
+/**
+ * This script inserts 239 CMS detectors into the database so that one can
+ * test the crawl speed of Yioop in a scenario that there are a moderate
+ * number of CMS detectors.
+ */
+/**
+ * Calculate base directory of script
+ * @ignore
+ */
+define("seekquarry\\yioop\\configs\\PARENT_DIR", substr(
+    dirname(realpath($_SERVER['PHP_SELF'])), 0,
+    -strlen("/tests")));
+define("seekquarry\\yioop\\configs\\BASE_DIR", C\PARENT_DIR . "/src");
+require_once C\BASE_DIR.'/configs/Config.php';
+/** For class autoload **/
+require_once C\PARENT_DIR ."/vendor/autoload.php";
+$cms_model = new M\CmsModel();
+//Add lots of CMS detectors
+$cms_detectors = getCmsDetectorEntries();
+print("Please wait this will take " . count($cms_detectors) . " seconds\n");
+foreach ($cms_detectors as $cms_detector) {
+    $name = $cms_detector[0];
+    $header = $cms_detector[1];
+    $cms_model->addCmsDetectors($name, $header, "");
+    sleep(1);
+}
+/**
+ * This function has an array of CMS detector values to return.
+ *
+ * @param array CMS detector values.
+ */
+function getCmsDetectorEntries()
+{
+    return [
+        ["1C-Bitrix","(?:<link[^>]+components/bitrix|(?:src|href)=\"/bitrix" .
+            "/(?:js|templates))"],
+        ["3DM","<title>3ware 3DM([\\d\\.]+)?\\;version:\\1"],
+        ["apps['ATG Web Commerce']","<[^>]+_DARGS"],
+        ["AdRiver","(?:<embed[^>]+(?:src=\"https?://mh\\d?\\.adriver\\.ru/|" .
+            "flashvars=\"[^\"]*(?:http:%3A//(?:ad|mh\\d?)\\.adriver\\.ru/|a" .
+            "driver_banner))|<(?:(?:iframe|img)[^>]+src|a[^>]+href)=\"https" .
+            "?://ad\\.adriver\\.ru/)"],
+        ["apps['Adobe CQ5']","<div class=\"[^\"]*parbase"],
+        ["apps['Adobe ColdFusion']","<!-- START headerTags\\.cfm"],
+        ["apps['Advanced Web Stats']","aws\\.src = [^<]+caphyon-analytics"],
+        ["Adzerk","<iframe [^>]*src=\"[^\"]+adzerk\\.net"],
+        ["apps['Apache HBase']","<style[^>]+static/hbase"],
+        ["apps['Apache Hadoop']","<style[^>]+static/hadoop"],
+        ["apps['Apache JSPWiki']","<html[^>]* xmlns:jspwiki="],
+        ["AppNexus","<(?:iframe|img)[^>]+adnxs\\.(?:net|com)"],
+        ["Arastta","Powered By <a href=\"[^>]+Arastta"],
+        ["apps['Arc Forum']","ping\\.src = node\\.href;\\s+[^>]+\\s+}\\s+</" .
+            "script>"],
+        ["Artifactory","<span class=\"version\">Artifactory(?: Pro)?(?: Pow" .
+            "er Pack)?(?: ([\\d.]+))?\\;version:\\1"],
+        ["apps['Atlassian Confluence']","Powered by <a href=[^>]+atlassian\\" .
+            ".com/software/confluence(?:[^>]+>Atlassian Confluence</a> ([\\d" .
+            ".]+))?\\;version:\\1"],
+        ["apps['Atlassian FishEye']","<title>(?:Log in to )?FishEye (?:and " .
+            "Crucible )?([\\d.]+)?</title>\\;version:\\1"],
+        ["apps['Atlassian Jira']","Powered by\\s+<a href=[^>]+atlassian\\.c" .
+            "om/(?:software/jira|jira-bug-tracking/)[^>]+>Atlassian\\s+JIRA" .
+            "(?:[^v]*v(?:ersion: )?(\\d+\\.\\d+(\\.\\d+)?))?\\;version:\\1"],
+        ["Avangate","<link[^>]* href=\"^https?://edge\\.avangate\\.net/"],
+        ["BIGACE","(?:Powered by <a href=\"[^>]+BIGACE|<!--\\s+Site is runn" .
+            "ing BIGACE)"],
+        ["Banshee","Built upon the <a href=\"[^>]+banshee-php\\.org/\">[a-z" .
+            "]+</a>(?:v([\\d.]+))?\\;version:\\1"],
+        ["BigDump","<!-- <h1>BigDump: Staggered MySQL Dump Importer ver\\. " .
+            "([\\d.b]+)\\;version:\\1"],
+        ["Bigcommerce","<link href=[^>]+cdn\\d+\\.bigcommerce\\.com/v"],
+        ["Bigware","(?:Diese <a href=[^>]+bigware\\.de|<a href=[^>]+/main_b" .
+            "igware_\\d+\\.php)"],
+        ["tv']","<(?:param|embed|iframe)[^>]+blip\\.tv/play"],
+        ["Bonfire","Powered by <a[^>]+href=\"https?://(?:www\\.)?cibonfire\\" .
+            ".com[^>]*>Bonfire v([^<]+)\\;version:\\1"],
+        ["Bugzilla","href=\"enter_bug\\.cgi\">"],
+        ["apps['Burning Board']","<a href=\"[^>]+woltlab\\.com[^<]+<strong>" .
+            "Burning Board"],
+        ["apps['Business Catalyst']","<!-- BC_OBNW -->"],
+        ["BuySellAds","<script[^>]*>[^<]+?bsa.src\\s*=\\s*['\"](?:https?:)?" .
+            "\\/{2}\\w\\d\\.buysellads\\.com\\/[\\w\\d\\/]+?bsa\\.js['\"]"],
+        ["CO2Stats","src=[^>]+co2stats\\.com/propres\\.php"],
+        ["apps['CS Cart']","&nbsp;Powered by (?:<a href=[^>]+cs-cart\\.com|" .
+            "CS-Cart)"],
+        ["apps['Carbon Ads']","<[a-z]+ [^>]*id=\"carbonads-container\""],
+        ["Cargo","<link [^>]+Cargo feed"],
+        ["Chamilo","\">Chamilo ([\\d.]+)</a>\\;version:\\1"],
+        ["CodeIgniter","<input[^>]+name=\"ci_csrf_token\"\\;version:2+"],
+        ["Contao","<!--[^>]+powered by (?:TYPOlight|Contao)[^>]*-->"],
+        ["Coppermine","<!--Coppermine Photo Gallery ([\\d.]+)\\;version:\\1"],
+        ["CubeCart","(?:Powered by <a href=[^>]+cubecart\\.com|<p[^>]+>Powe" .
+            "red by CubeCart)"],
+        ["apps['DM Polopoly']","<(?:link [^>]*href|img [^>]*src)=\"/polopol" .
+            "y_fs/"],
+        ["DNN","<!-- by DotNetNuke Corporation"],
+        ["DTG","<a[^>]+Site Powered by DTG"],
+        ["Demandware","<[^>]+demandware\\.edgesuite"],
+        ["DirectAdmin","<a[^>]+>DirectAdmin</a> Web Control Panel"],
+        ["Disqus","<div[^>]+id=\"disqus_thread\""],
+        ["Django","(?:powered by <a[^>]+>Django ?([\\d.]+)?|<input[^>]*name" .
+            "=[\"']csrfmiddlewaretoken[\"'][^>]*>)\\;version:\\1"],
+        ["Dokeos","(?:Portal <a[^>]+>Dokeos|@import \"[^\"]+dokeos_blue)"],
+        ["Doxygen","(?:<!-- Generated by Doxygen ([\\d.]+)|<link[^>]+doxyge" .
+            "n\\.css)\\;version:\\1"],
+        ["DreamWeaver","(?:<!--[^>]*(?:InstanceBeginEditable|Dreamweaver([^" .
+            ">]+)target|DWLayoutDefaultTable)|function MM_preloadImages\\(\\" .
+            ") \\{)\\;version:\\1"],
+        ["Drupal","<(?:link|style)[^>]+sites/(?:default|all)/(?:themes|modu" .
+            "les)/"],
+        ["apps['Drupal Commerce']","<[^>]+(?:id=\"block[_-]commerce[_-]cart" .
+            "[_-]cart|class=\"commerce[_-]product[_-]field)"],
+        ["ELOG","<title>ELOG Logbook Selection</title>"],
+        ["Epoch","<link[^>]+?href=\"[^\"]+epoch(?:\\.min)?\\.css"],
+        ["apps['FAST ESP']","<form[^>]+id=\"fastsearch\""],
+        ["apps['FAST Search for SharePoint']","<input[^>]+ name=\"Parametri" .
+            "cSearch"],
+        ["FWP","<!--\\s+FwP Systems"],
+        ["apps['Fact Finder']","<!-- Factfinder"],
+        ["FlexCMP","<!--[^>]+FlexCMP[^>v]+v\\. ([\\d.]+)\\;version:\\1"],
+        ["FluxBB","Powered by (?:<strong>)?<a href=\"[^>]+fluxbb"],
+        ["Flyspray","(?:<a[^>]+>Powered by Flyspray|<map id=\"projectsearch" .
+            "form)"],
+        ["apps['Font Awesome']","<link[^>]* href=[^>]+font-awesome(?:\\.min" .
+            ")?\\.css"],
+        ["Fortune3","(?:<link [^>]*href=\"[^\\/]*\\/\\/www\\.fortune3\\.com" .
+            "\\/[^\"]*siterate\\/rate\\.css|Powered by <a [^>]*href=\"[^\"]" .
+            "+fortune3\\.com)"],
+        ["FreeTextBox","/<!--\\s*\\*\\s*FreeTextBox v\\d+ \\(([.\\d]+)(?:(?" .
+            ":.|)+?<!--\\s*\\*\\s*License Type: (Distribution|Professional" .
+            ")License)?/i\\;version:\\1 \\2"],
+        ["FrontPage","<html[^>]+urn:schemas-microsoft-com:office:office"],
+        ["apps['GX WebManager']","<!--\\s+Powered by GX"],
+        ["Gallery","<div id=\"gsNavBar\" class=\"gcBorder1\">"],
+        ["Gambio","(?:<link[^>]* href=\"templates/gambio/|<a[^>]content\\.p" .
+            "hp\\?coID=\\d|<!-- gambio eof -->|<!--[\\s=]+Shopsoftware by G" .
+            "ambio GmbH \\(c\\))"],
+        ["Glyphicons","(?:<link[^>]* href=[^>]+glyphicons(?:\\.min)?\\.css|" .
+            "<img[^>]* src=[^>]+glyphicons)"],
+        ["apps['Google Font API']","<link[^>]* href=[^>]+fonts\\.(?:googlea" .
+            "pis|google)\\.com"],
+        ["apps['Google Tag Manager']","googletagmanager\\.com/ns\\.html[^>]" .
+            "+></iframe>"],
+        ["Gravatar","<[^>]+gravatar\\.com/avatar/"],
+        ["apps['Green Valley CMS']","<img[^>]+/dsresource\\?objectid="],
+        ["Handlebars","<[^>]*type=[^>]text\\/x-handlebars-template"],
+        ["HeadJS","<[^>]*data-headjs-load"],
+        ["Highcharts","<svg[^>]*><desc>Created with Highcharts ([\\d.]*)\\;" .
+            "version:\\1"],
+        ["Highstock","<svg[^>]*><desc>Created with Highstock ([\\d.]*)\\;ve" .
+            "rsion:\\1"],
+        ["Hippo"," <[^>]+/binaries/(?:[^/]+/)*content/gallery/"],
+        ["HubSpot","<!-- Start of Async HubSpot"],
+        ["Hybris","<[^>]+(?:/sys_master/|/hybr/|/_ui/desktop/)"],
+        ["IPB","<link[^>]+ipb_[^>]+\\.css"],
+        ["InProces","<!-- CSS InProces Portaal default -->"],
+        ["Indexhibit","<(?:link|a href) [^>]+ndxz-studio"],
+        ["Indico","Powered by\\s+(?:CERN )?<a href=\"http://(?:cdsware\\.ce" .
+            "rn\\.ch/indico/|indico-software\\.org|cern\\.ch/indico)\">(?:C" .
+            "DS )?Indico( [\\d\\.]+)?\\;version:\\1"],
+        ["Invenio","(?:Powered by|System)\\s+(?:CERN )?<a (?:class=\"footer" .
+            "\" )?href=\"http://(?:cdsware\\.cern\\.ch(?:/invenio)?|invenio" .
+            "-software\\.org|cern\\.ch/invenio)(?:/)?\">(?:CDS )?Invenio</a" .
+            ">\\s*v?([\\d\\.]+)?\\;version:\\1"],
+        ["Ionicons","<link[^>]* href=[^>]+ionicons(?:\\.min)?\\.css"],
+        ["apps['JTL Shop']","(?:<input[^>]+name=\"JTLSHOP|<a href=\"jtl\\.p" .
+            "hp)"],
+        ["Joomla","(?:<div[^>]+id=\"wrapper_r\"|<[^>]+(?:feed|components)/c" .
+            "om_|<table[^>]+class=\"pill)\\;confidence:50"],
+        ["K2","<!--(?: JoomlaWorks \"K2\"| Start K2)"],
+        ["apps['Kendo UI']","<link[^>]*\\s+href=[^>]*styles/kendo\\.common(" .
+            "?:\\.min)?\\.css[^>]*/>"],
+        ["apps['Koala Framework']","<!--[^>]+This website is powered by Koa" .
+            "la Web Framework CMS"],
+        ["Koken","<html lang=\"en\" class=\"k-source-essays k-lens-essays\">"],
+        ["Koobi","<!--[^K>-]+Koobi ([a-z\\d.]+)\\;version:\\1"],
+        ["Less","<link[^>]+ rel=\"stylesheet/less\""],
+        ["apps['LightMon Engine']","<!-- Lightmon Engine Copyright Lightmon"],
+        ["Lightbox","<link [^>]*href=\"[^\"]+lightbox(?:\\.min)?\\.css"],
+        ["Lithium"," <a [^>]+Powered by Lithium"],
+        ["Livefyre","<[^>]+(?:id|class)=\"livefyre"],
+        ["Liveinternet","<script[^<>]*>[^]{0,128}?src\\s*=\\s*['\"]//counte" .
+            "r\\.yadro\\.ru/hit(?:;\\S+)?\\?(?:t\\d+\\.\\d+;)?r"],
+        ["Locomotive","<link[^>]*/sites/[a-z\\d]{24}/theme/stylesheets"],
+        ["MODx","<a[^>]+>Powered by MODx</a>"],
+        ["MantisBT","<img[^>]+ alt=\"Powered by Mantis Bugtracker"],
+        ["apps['Materialize CSS']","<link[^>]* href=\"[^\"]*materialize(?:\\" .
+            ".min)?\\.css"],
+        ["MediaWiki","(?:<a[^>]+>Powered by MediaWiki</a>|<[^>]+id=\"t-spec" .
+            "ialpages)"],
+        ["Meebo","(?:<iframe id=\"meebo-iframe\"|Meebo\\('domReady'\\))"],
+        ["Meteor","<link[^>]+__meteor-css__"],
+        ["Methode","<!-- Methode uuid: \"[a-f\\d]+\" ?-->"],
+        ["NET']","<input[^>]+name=\"__VIEWSTATE"],
+        ["MiniBB","<a href=\"[^\"]+minibb[^<]+</a>[^<]+<!--End of copyrigh" .
+            "t link"],
+        ["CMS']","(<script|link)[^>]*mg-(core|plugins|templates)"],
+        ["Mollom","<img[^>]+\\.mollom\\.com"],
+        ["Moodle","<img[^>]+moodlelogo"],
+        ["MyBB","(?:<script [^>]+\\s+<!--\\s+lang\\.no_new_posts|<a[^>]* ti" .
+            "tle=\"Powered By MyBB)"],
+        ["NOIX","(?:<[^>]+(?:src|href)=[^>]*/media/noix|<!-- NOIX)"],
+        ["NVD3","<link[^>]* href=[^>]+nv\\.d3(?:\\.min)?\\.css"],
+        ["apps['OWL Carousel']","<link [^>]*href=\"[^\"]+owl.carousel(?:\\." .
+            "min)?\\.css"],
+        ["apps['OXID eShop']","<!--[^-]*OXID eShop"],
+        ["Odoo","<link[^>]* href=[^>]+/web/css/(?:web\\.assets_common/|webs" .
+            "ite\\.assets_frontend/)\\;confidence:25"],
+        ["apps['Open Web Analytics']","<!-- (?:Start|End) Open Web Analytic" .
+            "s Tracker -->"],
+        ["OpenCart","(?:index\\.php\\?route=[a-z]+/|Powered By <a href=\"[^" .
+            ">]+OpenCart)"],
+        ["OpenCms","<link href=\"/opencms/"],
+        ["apps['OpenText Web Solutions']","<!--[^>]+published by Open Text " .
+            "Web Solutions"],
+        ["apps['Outlook Web App']","<link\\s[^>]*href=\"[^\"]*?([\\d.]+)/th" .
+            "emes/resources/owafont\\.css\\;version:\\1"],
+        ["js']","<\\/div>\\s*<!-- outerContainer -->\\s*<div\\s*id=\"printC" .
+            "ontainer\"><\\/div>"],
+        ["PHP-Fusion","Powered by <a href=\"[^>]+php-fusion"],
+        ["PHP-Nuke","<[^>]+Powered by PHP-Nuke"],
+        ["PayPal","<input[^>]+_s-xclick"],
+        ["Penguin","<link[^>]+?href=\"[^\"]+penguin(?:\\.min)?\\.css"],
+        ["Percussion","<[^>]+class=\"perc-region\""],
+        ["Pligg","<span[^>]+id=\"xvotes-0"],
+        ["Plura","<iframe src=\"[^>]+pluraserver\\.com"],
+        ["Polymer","(?:<polymer-[^>]+|<link[^>]+rel=\"import\"[^>]+/polymer" .
+            "\\.html\")"],
+        ["Posterous","<div class=\"posterous"],
+        ["Powergap","<a[^>]+title=\"POWERGAP"],
+        ["PrestaShop","Powered by <a\\s+[^>]+>PrestaShop"],
+        ["apps['Project Wonderful']","<div[^>]+id=\"pw_adbox_"],
+        ["apps['Pure CSS']","<link[^>]+(?:([\\d.])+/)?pure(?:-min)?\\.css\\;" .
+            "version:\\1"],
+        ["CMS']","<a href=\"[^>]+opensolution\\.org/\">CMS by"],
+        ["Cart']","<a href=\"[^>]+opensolution\\.org/\">(?:Shopping cart by" .
+            "|Sklep internetowy)"],
+        ["RainLoop","<meta [^>]*(?:content=\"([^\"]+)[^>]+ id=\"rlAppVersio" .
+            "n\"|id=\"rlAppVersion\"[^>]+ content=\"([^\"]+))\\;version:\\1" .
+            "?\\1:\\2"],
+        ["apps['RBS Change']","<html[^>]+xmlns:change="],
+        ["RDoc","<link[^>]+href=\"[^\"]*rdoc-style\\.css"],
+        ["Reddit","(?:<a[^>]+Powered by Reddit|powered by <a[^>]+>reddit<)"],
+        ["Redmine","Powered by <a href=\"[^>]+Redmine"],
+        ["RoundCube","<title>RoundCube"],
+        ["apps['SDL Tridion']","<img[^>]+_tcm\\d{2,3}-\\d{6}\\."],
+        ["apps['SOBI 2']","(?:<!-- start of Sigsiu Online Business Index|<d" .
+            "iv[^>]* class=\"sobi2)"],
+        ["apps['SQL Buddy']","(?:<title>SQL Buddy</title>|<[^>]+onclick=\"s" .
+            "ideMainClick\\(\"home\\.php)"],
+        ["Semantic-ui","(?:<div class=\"ui\\s[^>]+\">)\\;confidence:30"],
+        ["apps['Sentinel License Monitor']","<title>Sentinel (?:Keys )?Lice" .
+            "nse Monitor</title>"],
+        ["Seoshop","<a[^>]+title=\"SEOshop"],
+        ["ShinyStat","<img[^>]*\\s+src=['\"]?https?://www\\.shinystat\\.com" .
+            "/cgi-bin/shinystat\\.cgi\\?[^'\"\\s>]*['\"\\s/>]"],
+        ["Shopatron","<body class=\"shopatron"],
+        ["Shopify","<link[^>]+=['\"]//cdn\\.shopify\\.com"],
+        ["SilverStripe","Powered by <a href=\"[^>]+SilverStripe"],
+        ["Sitecore","<img[^>]+src=\"[^>]*/~/media/[^>]+\\.ashx"],
+        ["Sizmek","(?:<a [^>]*href=\"[^/]*//[^/]*serving-sys\\.com/|<img [^" .
+            ">]*src=\"[^/]*//[^/]*serving-sys\\.com/)"],
+        ["Slimbox","<link [^>]*href=\"[^/]*slimbox(?:-rtl)?\\.css"],
+        ["apps['Slimbox 2']","<link [^>]*href=\"[^/]*slimbox2(?:-rtl)?\\.css"],
+        ["apps['Smart Ad Server']","<img[^>]+smartadserver\\.com\\/call"],
+        ["SmartSite","<[^>]+/smartsite\\.(?:dws|shtml)\\?id="],
+        ["Solodev","<div class='dynamicDiv' id='dd\\.\\d\\.\\d'>"],
+        ["Splunk","<p class=\"footer\">&copy; [-\\d]+ Splunk Inc\\.(?: Splu" .
+            "nk ([\\d\\.]+( build [\\d\\.]*\\d)?))?[^<]*</p>\\;version:\\1"],
+        ["Spree","(?:<link[^>]*/assets/store/all-[a-z\\d]{32}\\.css[^>]+>|<" .
+            "script>\\s*Spree\\.(?:routes|translations|api_key))"],
+        ["SquirrelMail","<small>SquirrelMail version ([.\\d]+)[^<]*<br \\;v" .
+            "ersion:\\1"],
+        ["apps['Squiz Matrix']","<!--\\s+Running (?:MySource|Squiz) Matrix"],
+        ["apps['Store Systems']","Shopsystem von <a href=[^>]+store-systems" .
+            "\\.de\"|\\.mws_boxTop"],
+        ["Stripe","<input[^>]+data-stripe"],
+        ["SweetAlert","<link[^>]+?href=\"[^\"]+sweet-alert(?:\\.min)?\\.css"],
+        ["Swiftlet","Powered by <a href=\"[^>]+Swiftlet"],
+        ["SyntaxHighlighter",
+            "(<script|<link)[^>]*sh(Core|Brush|ThemeDefault)"],
+        ["TWiki","<img [^>]*(?:title|alt)=\"This site is powered by the TWi" .
+            "ki collaboration platform"],
+        ["apps['TYPO3 CMS']","<(?:script[^>]+ src|link[^>]+ href)=[^>]+typo" .
+            "3temp/"],
+        ["apps['TYPO3 Neos']","<html[^>]+xmlns:typo3=\"[^\"]+Flow/Packages/" .
+            "Neos/"],
+        ["TeamCity","<span class=\"versionTag\"><span class=\"vWord\">Versi" .
+            "on</span> ([\\d\\.]+)\\;version:\\1"],
+        ["TiddlyWiki","<[^>]*type=[^>]text\\/vnd\\.tiddlywiki"],
+        ["Titan","<script[^>]+>var titan"],
+        ["Trac","<a id=\"tracpowered"],
+        ["Tumblr","<iframe src=\"[^>]+tumblr\\.com"],
+        ["apps['Twitter Bootstrap']","<style>/\\*!\\* Bootstrap v(\\d\\.\\d" .
+            "\\.\\d)\\;version:\\1"],
+        ["UltraCart","<form [^>]*action=\"[^\"]*\\/cgi-bin\\/UCEditor\\?(?:" .
+            "[^\"]*&)?merchantId=[^\"]"],
+        ["Umbraco","powered by <a href=[^>]+umbraco"],
+        ["VP-ASP","<a[^>]+>Powered By VP-ASP Shopping Cart</a>"],
+        ["Vanilla","<body id=\"(?:DiscussionsPage|vanilla)"],
+        ["Veoxa","<img [^>]*src=\"[^\"]+tracking\\.veoxa\\.com"],
+        ["VideoJS","<div[^>]+class=\"video-js+\">"],
+        ["Vignette","<[^>]+=\"vgn-?ext"],
+        ["Vimeo","(?:<(?:param|embed)[^>]+vimeo\\.com/moogaloop|<iframe[^>]" .
+            "player\\.vimeo\\.com)"],
+        ["VirtueMart","<div id=\"vmMainPage"],
+        ["Volusion","<link [^>]*href=\"[^\"]*/vspfiles/"],
+        ["apps['W3 Total Cache']","<!--[^>]+W3 Total Cache"],
+        ["apps['WP Rocket']","<!--[^>]+WP Rocket"],
+        ["apps['Web Optimizer']","<title [^>]*lang=\"wo\">"],
+        ["Webtrends","<img[^>]+id=\"DCSIMG\"[^>]+webtrends"],
+        ["Wikispaces","<script[^>]*>[^<]*session_url:\\s*'https://session\\." .
+            "wikispaces\\.com/"],
+        ["WikkaWiki","Powered by <a href=\"[^>]+WikkaWiki"],
+        ["apps['Wolf CMS']","(?:<a href=\"[^>]+wolfcms\\.org[^>]+>Wolf CMS(" .
+            "?:</a>)? inside|Thank you for using <a[^>]+>Wolf CMS)"],
+        ["WooCommerce","<!-- WooCommerce"],
+        ["WordPress","<link rel=[\"']stylesheet[\"'] [^>]+wp-(?:content|inc" .
+            "ludes)"],
+        ["apps['WordPress Super Cache']","<!--[^>]+WP-Super-Cache"],
+        ["apps['Wowza Media Server']","<title>Wowza Media Server \\d+ ((\\w" .
+            "+ Edition )?\\d+\\.[\\d\\.]+( build\\d+)?)?\\;version:\\1"],
+        ["X-Cart","Powered by X-Cart(?: (\\d+))? <a[^>]+href=\"http://www\\." .
+            "x-cart\\.com/\"[^>]*>\\;version:\\1"],
+        ["XAMPP","<title>XAMPP( Version ([\\d\\.]+))?</title>\\;version:\\1" .
+            "\\;confidence:90"],
+        ["XMB","<!-- Powered by XMB"],
+        ["XenForo","(?:jQuery\\.extend\\(true, XenForo|Forum software by Xe" .
+            "nForo&trade;|<!--XF:branding|<html[^>]+id=\"XenForo\")"],
+        ["apps['YUI Doc']","(?:<html[^>]* yuilibrary\\.com/rdf/[\\d.]+/yui\\" .
+            ".rdf|<body[^>]+class=\"yui3-skin-sam)"],
+        ["YaBB","Powered by <a href=\"[^>]+yabbforum"],
+        ["apps['Yahoo Advertising']","<iframe[^>]+adserver\\.yahoo\\.com"],
+        ["apps['Yahoo! Ecommerce']","<link[^>]+store\\.yahoo\\.net"],
+        ["Direct']","<yatag class=\"ya-partner__ads\">"],
+        ["Yii","Powered by <a href=\"http://www.yiiframework.com/\" rel=\"e" .
+            "xternal\">Yii Framework</a>"],
+        ["apps['Yoast SEO']","<!-- This site is optimized with the Yoast"],
+        ["YouTube","<(?:param|embed|iframe)[^>]+youtube(?:-nocookie)?\\.com" .
+            "/(?:v|embed)"],
+        ["ZK","<!-- ZK [\\.\\d\\s]+-->"],
+        ["apps['ZURB Foundation']","<link[^>]+foundation[^>\"]+css"],
+        ["Zabbix","<body[^>]+zbxCallPostScripts"],
+        ["Zanox","<img [^>]*src=\"[^\"]+ad\\.zanox\\.com"],
+        ["Zeuscart","<form name=\"product\" method=\"post\" action=\"[^\"]+" .
+            "\\?do=addtocart&prodid=\\d+\"(?!<\\/form>.)+<input type=\"hidd" .
+            "en\" name=\"addtocart\" value=\"\\d+\">"],
+        ["cPanel","<!-- cPanel"],
+        ["cgit","<[^>]+id='cgit'"],
+        ["comScore","<iframe[^>]* (?:id=\"comscore\"|scr=[^>]+comscore)|\\." .
+            "scorecardresearch\\.com/beacon\\.js|COMSCORE\\.beacon"],
+        ["gitweb","<!-- git web interface version"],
+        ["nopCommerce","(?:<!--Powered by nopCommerce|Powered by: <a[^>]+no" .
+            "pcommerce)"],
+        ["osCSS","<body onload=\"window\\.defaultStatus='oscss templates';\""],
+        ["osCommerce","(?:<a[^>]*(?:\\?|&)osCsid|Powered by (?:<[^>]+>)?osC" .
+            "ommerce</a>|<[^>]+class=\"[^>]*infoBoxHeading)"],
+        ["ownCloud","<a href=\"https://owncloud.com\" target=\"_blank\">own" .
+            "Cloud Inc.</a><br/>Your Cloud, Your Data, Your Way!"],
+        ["apps['papaya CMS']","<link[^>]*/papaya-themes/"],
+        ["phpAlbum","<!--phpalbum ([.\\d\\s]+)-->\\;version:\\1"],
+        ["phpBB","(?:Powered by <a[^>]+phpbb|<a[^>]+phpbb[^>]+class=\\.copy" .
+            "right|    phpBB style name|<[^>]+styles/(?:sub|pro)silver/theme|<" .
+            "img[^>]+i_icon_mini|<table class=\"forumline)"],
+        ["phpDocumentor","<!-- Generated by phpDocumentor"],
+        ["phpMyAdmin","(?: \\| phpMyAdmin ([\\d.]+)<\\/title>|PMA_sendHeade" .
+            "rLocation\\(|<link [^>]*href=\"[^\"]*phpmyadmin\\.css\\.php)\\;" .
+            "version:\\1"],
+        ["phpPgAdmin","(?:<title>phpPgAdmin</title>|<span class=\"appname\"" .
+            ">phpPgAdmin)"],
+        ["phpwind","Powered by <a href=\"[^\"]+phpwind\\.net"],
+        ["prettyPhoto","(?:<link [^>]*href=\"[^\"]*prettyPhoto(?:\\.min)?\\." .
+            "css|<a [^>]*rel=\"prettyPhoto)"],
+        ["punBB","Powered by <a href=\"[^>]+punbb"],
+        ["reCAPTCHA","(?:<div[^>]+id=\"recaptcha_image|<link[^>]+recaptcha|" .
+            "document\\.getElementById\\('recaptcha')"],
+        ["viennaCMS","powered by <a href=\"[^>]+viennacms"],
+        ["js']","<link[^>]+?href=\"[^\"]+vis(?:\\.min)?\\.css"],
+        ["xCharts","<link[^>]* href=\"[^\"]*xcharts(?:\\.min)?\\.css"],
+        ["xtCommerce","<div class=\"copyright\">[^<]+<a[^>]+>xt:Commerce"],
+    ];
+}
\ No newline at end of file
diff --git a/tests/test_files/cms_detector/Joomla01.txt b/tests/test_files/cms_detector/Joomla01.txt
new file mode 100644
index 0000000..b5d7e0d
--- /dev/null
+++ b/tests/test_files/cms_detector/Joomla01.txt
@@ -0,0 +1,167 @@
+﻿<!doctype html>
+<html xmlns:og="http://ogp.me/ns#"  xml:lang="en-us" lang="en-us">
+<head>
+<link href="https://ace.nd.edu/" rel="canonical" />
+
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+       <meta name="p:domain_verify" content="5c60f6c3627eb0964ee4106391f54e26"/>
+      <base href="https://ace.nd.edu/" />
+  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+  <meta name="keywords" content="Catholic education, Catholic schools, ACE, Alliance for Catholic Education, service, certification, masters degree, professional development, consulting" />
+  <meta name="robots" content="index, follow" />
+  <meta name="description" content="ACE, the Alliance for Catholic Education, sustains, strengthens, and transforms Catholic schools." />
+  
+  <title>Alliance for Catholic Education</title>
+  <link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+  <link rel="stylesheet" href="/media/modals/css/bootstrap.min.css" type="text/css" />
+  <link rel="stylesheet" href="/templates/ace/css-compiled/menu-866aa62ff6c16fcab414a76f625ee345.css" type="text/css" />
+  <link rel="stylesheet" href="/templates/ace/css/oneweb.css" type="text/css" />
+  <link rel="stylesheet" href="/templates/ace/css-compiled/master-53f95a45aa1d40970d8d57ed4b5a8aec.css" type="text/css" />
+  <link rel="stylesheet" href="/templates/ace/css/ace-custom.css" type="text/css" />
+  <link rel="stylesheet" href="/media/mod_cmc/css/cmc.css" type="text/css" />
+  <link rel="stylesheet" href="/media/mod_cmc/css/bootstrap-form.css" type="text/css" />
+  <link rel="stylesheet" href="/modules/mod_zooitem/tmpl/list-h/style.css?ver=20160504" type="text/css" />
+  <link rel="stylesheet" href="https://ace.nd.edu/components/com_acesearch/assets/css/acesearch.css" type="text/css" />
+  <link rel="stylesheet" href="https://ace.nd.edu/plugins/system/exttweetable/assets/css/default.css" type="text/css" />
+  <link rel="stylesheet" href="/media/widgetkit/wk-styles-6bfec395.css" type="text/css" id="wk-styles-css" />
+  <style type="text/css">
+.cloaked_email span:before { content: attr(data-ep-a60a5); } .cloaked_email span:after { content: attr(data-ep-beb4b); }
+#rt-logo {background: url(/images/logo-text.png) 50% 0 no-repeat !important;}
+#rt-logo {width: 390px;height: 30px;}
+
+  </style>
+  <script src="/media/jui/js/jquery.min.js" type="text/javascript"></script>
+  <script src="/media/jui/js/jquery-noconflict.js" type="text/javascript"></script>
+  <script src="/media/jui/js/jquery-migrate.min.js" type="text/javascript"></script>
+  <script src="/media/system/js/caption.js" type="text/javascript"></script>
+  <script src="/media/jui/js/bootstrap.min.js" type="text/javascript"></script>
+  <script src="/media/modals/js/jquery.touchSwipe.min.js" type="text/javascript"></script>
+  <script src="/media/modals/js/jquery.colorbox-min.js" type="text/javascript"></script>
+  <script src="/media/modals/js/script.min.js?v=7.0.0.p" type="text/javascript"></script>
+  <script src="/media/system/js/mootools-core.js" type="text/javascript"></script>
+  <script src="/media/system/js/core.js" type="text/javascript"></script>
+  <script src="/media/system/js/mootools-more.js" type="text/javascript"></script>
+  <script src="/libraries/gantry/js/browser-engines.js" type="text/javascript"></script>
+  <script src="/modules/mod_roknavmenu/themes/default/js/rokmediaqueries.js" type="text/javascript"></script>
+  <script src="/modules/mod_roknavmenu/themes/default/js/sidemenu.js" type="text/javascript"></script>
+  <script src="/media/system/js/punycode.js" type="text/javascript"></script>
+  <script src="/media/system/js/validate.js" type="text/javascript"></script>
+  <script src="/media/mod_cmc/js/cmc.js" type="text/javascript"></script>
+  <script src="/media/system/js/html5fallback.js" type="text/javascript"></script>
+  <script src="/modules/mod_zooitem/mod_zooitem.js?ver=20160504" type="text/javascript"></script>
+  <script src="/media/widgetkit/uikit-53ef70f2.js" type="text/javascript"></script>
+  <script src="/media/widgetkit/wk-scripts-63f12e27.js" type="text/javascript"></script>
+  <script type="text/javascript">
+jQuery(window).on('load',  function() {
+				new JCaption('img.caption');
+			});
+;
+var modals_class = 'modal_link'; var modals_disable_on_mobile = 0; var modals_mobile_max_width = 767; var modals_open_by_url = ''; var modals_defaults = { opacity: '0.8',maxWidth: '95%',maxHeight: '95%',fixed: 'true',current: '{current} / {total}',previous: 'previous',next: 'next',close: 'close',xhrError: 'This content failed to load.',imgError: 'This image failed to load.' };
+var emailProtector=emailProtector||{};emailProtector.addCloakedMailto=function(g,l){var h=document.querySelectorAll("."+g);for(i=0;i<h.length;i++){var b=h[i],k=b.getElementsByTagName("span"),e="",c="";b.className=b.className.replace(" "+g,"");for(var f=0;f<k.length;f++)for(var d=k[f].attributes,a=0;a<d.length;a++)0===d[a].nodeName.toLowerCase().indexOf("data-ep-a")&&(e+=d[a].value),0===d[a].nodeName.toLowerCase().indexOf("data-ep-b")&&(c=d[a].value+c);if(!c)break;b.innerHTML=e+c;if(!l)break;b.parentNode.href="mailto:"+e+c}};
+jQuery(document).ready(function() {
+    new cmc("#cmc-signup-764");
+});
+
+
+jQuery(document).ready(function(){
+
+      jQuery('[data-tweetable]').tweetable({
+        url:        'https://ace.nd.edu/',
+        via:        'aceatnd',
+        related:    'aceatnd'
+    });
+});
+
+
+  </script>
+  <script type="text/javascript">
+    (function() {
+      Joomla.JText.load({"JLIB_FORM_FIELD_INVALID":"Invalid field:&#160"});
+    })();
+  </script>
+  <script type = "text/javascript" src = "https://ace.nd.edu/plugins/system/exttweetable/assets/js/jquery.tweetable.js"></script>
+
+
+<!-- Google sitename markup-->
+<script type="application/ld+json">
+{
+  "@context" : "http://schema.org",
+  "@type" : "WebSite",
+  "name" : "Alliance for Catholic Education",
+    "url" : "https://ace.nd.edu/"
+}
+</script>
+<!-- End of Google sitename markup-->
+
+
+  <meta property="og:locale" content="en_GB" />
+  <meta property="og:title" content="Alliance for Catholic Education" />
+  <meta property="og:description" content="ACE, the Alliance for Catholic Education, sustains, strengthens, and transforms Catholic schools." />
+  <meta property="og:type" content="website" />
+  <meta property="og:url" content="https://ace.nd.edu/" />
+  <meta property="og:image" content="https://ace.nd.edu/images/ace-logo.jpg" />
+  <meta property="og:image:secure_url" content="https://ace.nd.edu/images/ace-logo.jpg" />
+  <meta property="og:site_name" content="Alliance for Catholic Education" />
+<!-- BEGIN Advanced Google Analytics - http://deconf.com/advanced-google-analytics-joomla/ -->
+
+<script type="text/javascript">
+(function($){
+	$(window).load(function() {
+		if (this._gat) {
+			tks = this._gat._getTrackers();
+			ga_track = function(p) {
+				for (i=0; i < tks.length; i++) {
+					var n = tks[i]._getName() !== "" ? tks[i]._getName()+"." : "";
+					a = [];
+					for (i2=0; i2 < p.length; i2++) {
+						var b = i2===0 ? n+p[i2] : p[i2];
+						a.push(b);
+					}
+					_gaq.push(a);
+				}
+			};
+			$('a').filter(function() {
+				return this.href.match(/.*\.(zip|mp3|mpeg|pdf|doc*|ppt*|xls*|jpeg|png|gif|tiff)(\?.*)?$/);
+			}).click(function(e) {
+				ga_track(['_trackEvent', 'download', 'click', this.href]);
+			});
+			$('a[href^="mailto"]').click(function(e) {
+				ga_track(['_trackSocial', 'email', 'send', this.href]);
+			 });
+			var loc = location.host.split('.');
+			while (loc.length > 2) { loc.shift(); }
+			loc = loc.join('.');
+			var localURLs = [
+							  loc,
+							  'ace.nd.edu'
+							];
+			$('a[href^="http"]').filter(function() {
+				for (var i = 0; i < localURLs.length; i++) {
+					if (this.href.indexOf(localURLs[i]) == -1) return this.href;
+				}
+			}).click(function(e) {
+				ga_track(['_trackEvent', 'outbound', 'click', this.href]);
+			});
+		}
+	});
+})(jQuery);
+</script>
+
+<script type="text/javascript">
+var _gaq = _gaq || [];
+_gaq.push(['_setAccount', 'UA-344381-18'], ['_setCustomVar',4,'user-type','guest',3], ['_trackPageview']);
+
+(function() {
+var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ga.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'stats.g.doubleclick.net/dc.js';
+var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+})();
+
+</script>
+<!-- END Advanced Google Analytics -->
+
+
+</head>
+<body>	
+</body>
+</html>
diff --git a/tests/test_files/cms_detector/SiteCore01.txt b/tests/test_files/cms_detector/SiteCore01.txt
new file mode 100644
index 0000000..67dbc3c
--- /dev/null
+++ b/tests/test_files/cms_detector/SiteCore01.txt
@@ -0,0 +1,302 @@
+﻿
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    
+    <meta name='viewport' content='width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1' />
+    <meta name="format-detection" content="telephone=no">
+
+    
+    <title>Home: Elliott Wave International</title>
+    <!-- SET: FAVICON -->
+    <link rel="shortcut icon" type="image/x-icon" href="/assets/images/fav-img.png">
+    <!-- END: FAVICON -->
+    <!-- SET: STYLESHEET -->
+
+    <link rel="stylesheet" type="text/css" href="/assets/bootstrap/dist/css/bootstrap.css"><!-- PV Update: originally /css/bootstrap.css -->
+    <link rel="stylesheet" type="text/css" href="/assets/css/x-chop-style.css"><!--Main CSS. Built by xChop--->
+    <link rel="stylesheet" type="text/css" href="/assets/css/responsive.css" media="all"><!--Enables responsives styles-->
+    <link rel="stylesheet" type="text/css" href="/assets/css/dd.css"><!--Controls Wizard-->
+    <link rel="stylesheet" type="text/css" href="/assets/css/jquery.bxslider.css"><!--Controls Carousel and Educational Products Slider-->
+    <link rel="stylesheet" type="text/css" href="/assets/css/ewi-custom.css"><!--PV Todo: EWI Custom CSS so that we don not mess with the original css. Convert to LESS to be referenced inside of newly created bootstrap.less. Possibly add this into /css/style.css in to this custom.less file -->
+    <link rel="stylesheet" type="text/css" href="/assets/css/font-awesome.css"><!--Scalable Vector Icons -->
+    <link rel="stylesheet" type="text/css" href="/assets/css/pstool.css" />
+    
+    
+
+<meta name="VIcurrentDateTime" content="635982433076648412" />
+<script type="text/javascript" src="/layouts/system/VisitorIdentification.js"></script>
+
+
+    <!-- END: STYLESHEET -->
+    <!-- SET: SCRIPTS -->
+    
+    <!-- Add jQuery library -->
+    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
+    <script src="/assets/bootstrap/dist/js/bootstrap.min.js"></script>
+    <script type="text/javascript" src="/assets/js/jquery.dd.min.js"></script>
+    <script src="/assets/js/less-1.6.0.min.js"></script>
+    <script type="text/javascript" src="/assets/js/bxslider/jquery.bxslider.js"></script>
+    <script type="text/javascript" src="/assets/js/ewi/utils.js"></script>
+    <script type="text/javascript" src="/assets/js/ewi/cart/cart.js"></script>
+    <script type="text/javascript" src="/assets/js/proservices/pstool.js"></script>
+
+	<!--<script type="text/javascript" src="/assets/js/fancybox/lib/jquery-1.9.0.min.js"></script>-->
+    
+	<!-- Add fancyBox main JS and CSS files -->
+    <script type="text/javascript" src="/assets/js/fancybox/source/jquery.fancybox.js?v=2.1.4"></script>
+    <link rel="stylesheet" type="text/css" href="/assets/js/fancybox/source/jquery.fancybox.css?v=2.1.4" media="screen" />
+    	
+
+	<script type="text/javascript">
+        //$.noConflict();
+        (function($) {
+
+
+        $(document).ready(function () {
+			/*
+			 *  Simple image gallery. Uses default settings
+			 */
+
+			$('.fancybox').fancybox();
+
+			/*
+			 *  Different effects
+			 */
+
+
+			$(".fancybox")
+            //.attr('rel', 'gallery')
+            .fancybox({
+                type: 'iframe',
+                autoSize: false,
+
+
+                beforeLoad: function () {
+                    this.width = parseInt(this.href.match(/width=[0-9]+/i)[0].replace('width=', ''));
+                    this.height = parseInt(this.href.match(/height=[0-9]+/i)[0].replace('height=', ''));
+                }
+            });
+
+			$(".fancypdf").fancybox({
+			    openEffect: 'none',
+			    closeEffect: 'none',
+			    autoSize: true,
+			    type: 'iframe',
+			    iframe: {
+			        preload: false // fixes issue with iframe and IE
+			    }
+
+			});
+
+			// Change title type, overlay closing speed
+			$(".fancybox-effects-a").fancybox({
+				helpers: {
+					title : {
+						type : 'outside'
+					},
+					overlay : {
+						speedOut : 0
+					}
+				}
+			});
+
+			// Disable opening and closing animations, change title type
+			$(".fancybox-effects-b").fancybox({
+				openEffect  : 'none',
+				closeEffect	: 'none',
+
+				helpers : {
+					title : {
+						type : 'over'
+					}
+				}
+			});
+
+			// Set custom style, close if clicked, change title type and overlay color
+			$(".fancybox-effects-c").fancybox({
+				wrapCSS    : 'fancybox-custom',
+				closeClick : true,
+
+				openEffect : 'none',
+
+				helpers : {
+					title : {
+						type : 'inside'
+					},
+					overlay : {
+						css : {
+							'background' : 'rgba(238,238,238,0.85)'
+						}
+					}
+				}
+			});
+
+			// Remove padding, set opening and closing animations, close if clicked and disable overlay
+			$(".fancybox-effects-d").fancybox({
+				padding: 0,
+
+				openEffect : 'elastic',
+				openSpeed  : 150,
+
+				closeEffect : 'elastic',
+				closeSpeed  : 150,
+
+				closeClick : true,
+
+				helpers : {
+					overlay : null
+				}
+			});
+
+			/*
+			 *  Button helper. Disable animations, hide close button, change title type and content
+			 */
+
+			$('.fancybox-buttons').fancybox({
+				openEffect  : 'none',
+				closeEffect : 'none',
+
+				prevEffect : 'none',
+				nextEffect : 'none',
+
+				closeBtn  : false,
+
+				helpers : {
+					title : {
+						type : 'inside'
+					},
+					buttons	: {}
+				},
+
+				afterLoad : function() {
+					this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : '');
+				}
+			});
+
+
+			/*
+			 *  Thumbnail helper. Disable animations, hide close button, arrows and slide to next gallery item if clicked
+			 */
+
+			$('.fancybox-thumbs').fancybox({
+				prevEffect : 'none',
+				nextEffect : 'none',
+
+				closeBtn  : false,
+				arrows    : false,
+				nextClick : true,
+
+				helpers : {
+					thumbs : {
+						width  : 50,
+						height : 50
+					}
+				}
+			});
+
+			/*
+			 *  Media helper. Group items, disable animations, hide arrows, enable media and button helpers.
+			*/
+			$('.fancybox-media')
+				.attr('rel', 'media-gallery')
+				.fancybox({
+					openEffect : 'none',
+					closeEffect : 'none',
+					prevEffect : 'none',
+					nextEffect : 'none',
+
+					arrows : false,
+					helpers : {
+						media : {},
+						buttons : {}
+					}
+				});
+
+			/*
+			 *  Open manually
+			 */
+
+			$("#fancybox-manual-a").click(function() {
+				$.fancybox.open('1_b.jpg');
+			});
+
+			$("#fancybox-manual-b").click(function() {
+				$.fancybox.open({
+					href : 'iframe.html',
+					type : 'iframe',
+					padding : 5
+				});
+			});
+
+			$("#fancybox-manual-c").click(function() {
+				$.fancybox.open([
+					{
+						href : '1_b.jpg',
+						title : 'My title'
+					}, {
+						href : '2_b.jpg',
+						title : '2nd title'
+					}, {
+						href : '3_b.jpg'
+					}
+				], {
+					helpers : {
+						thumbs : {
+							width: 75,
+							height: 50
+						}
+					}
+				});
+			});
+
+        });
+        })(jQuery);
+
+	</script>
+	<style type="text/css">
+		.fancybox-custom .fancybox-skin {
+			box-shadow: 0 0 50px #222;
+		}
+	</style>
+    <script type="text/javascript">
+        //$.noConflict();
+        (function (jQuery) {
+            jQuery(document).ready(function () {
+	
+		    jQuery("#aLink").fancybox({
+				'width'             : '80%',
+				'height'            : '75%',
+				'autoScale'         : false,
+				'transitionIn'      : 'none',
+				'transitionOut'     : 'none',
+				'type'              : 'iframe',
+				'href'              : 'http://www.elliottwave.com'
+			});
+			
+			//launch on load after 5 second delay
+		    window.setTimeout('jQuery("#aLink")', 5000);
+	
+		});
+		})(jQuery);
+
+	</script>   
+    
+    <!-- GA -->
+            <script type="text/javascript">
+                (function (i, s, o, g, r, a, m) {
+                    i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
+                        (i[r].q = i[r].q || []).push(arguments)
+                    }, i[r].l = 1 * new Date(); a = s.createElement(o),
+                    m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
+                })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
+
+                ga('create', 'UA-1202085-30', 'auto');
+                ga('send', 'pageview');
+            </script>
+
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/test_files/cms_detector/SiteCore02.txt b/tests/test_files/cms_detector/SiteCore02.txt
new file mode 100644
index 0000000..3145b0f
--- /dev/null
+++ b/tests/test_files/cms_detector/SiteCore02.txt
@@ -0,0 +1,47 @@
+﻿
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>Personal &amp; Business Banking - Banks in Delaware | WSFS Bank</title>
+
+    <link rel="shortcut icon" href="/_assets/wsfs/images/favicon.png" />
+    <link rel="apple-touch-icon" href="/_assets/wsfs/images/favicon.png" />
+
+    <meta name="description" content="WSFS Bank, the oldest, locally-managed bank headquartered in Delaware, offers banking and wealth management solutions for personal and business Customers." />
+
+    <link href='//fonts.googleapis.com/css?family=Open+Sans:400,600,400italic,600italic,700,700italic' rel='stylesheet' type='text/css'>
+
+    <script src="https://use.typekit.net/gwi4uaz.js"></script>
+    <script>try { Typekit.load({ async: true }); } catch (e) { }</script>
+
+    <!-- Bootstrap -->
+    <link href="/_assets/wsfs/css/styles.min.css" rel="stylesheet">
+
+        <script src="/scripts/config.js"></script>
+        <script data-main="app" src="/scripts/require.js"></script>
+
+    <!-- Google Tag Manager -->
+    <noscript>
+        <iframe src="//www.googletagmanager.com/ns.html?id=GTM-NHDLCJ" height="0" width="0" style="display:none;visibility:hidden"></iframe>
+    </noscript>
+    <script>
+        (function (w, d, s, l, i) {w[l] = w[l] || []; w[l].push({'gtm.start':
+        new Date().getTime(), event: 'gtm.js'}); var f = d.getElementsByTagName(s)[0],
+        j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src =
+        '//www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
+        })(window, document, 'script', 'dataLayer', 'GTM-NHDLCJ');
+    </script>
+    <!-- End Google Tag Manager -->
+
+    
+<meta name="VIcurrentDateTime" content="635982433235498039" />
+<script type="text/javascript" src="/layouts/system/VisitorIdentification.js"></script>
+
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/test_files/cms_detector/Yioop01.txt b/tests/test_files/cms_detector/Yioop01.txt
new file mode 100644
index 0000000..da3daf0
--- /dev/null
+++ b/tests/test_files/cms_detector/Yioop01.txt
@@ -0,0 +1,62 @@
+﻿    <!DOCTYPE html>
+    <html lang="en-US" dir="ltr">
+        <head>
+        <title>PHP Search Engine - Yioop!</title>
+            <meta name="description" content="PHP Crawler and Search Engine Software" />
+        <meta name="Author" content="Chris Pollett" />
+        <meta charset="utf-8" />
+                <link rel="icon" href="https://yioop.com/favicon.ico" />
+        <link rel="stylesheet" type="text/css"
+            href="https://www.yioop.com/css/search.css" />
+                <link rel="stylesheet" type="text/css"
+            href="https://www.yioop.com/?c=resource&a=get&f=css&n=auxiliary.css" />
+                <link rel="search" type="application/opensearchdescription+xml"
+            href="https://yioop.com/yioopbar.xml"
+            title="Content search" />
+                <style type="text/css">
+                    body
+            {
+                background-color: #FFFFFF;
+            }
+                        .frame,
+            .icon-upload,
+            .current-activity,
+            .light-content,
+            .small-margin-current-activity,
+            .suggest-list li span.unselected
+            {
+                background-color: #FFFFFF;
+            }
+            .icon-upload
+            {
+                color: #FFFFFF;
+            }
+                        .activity-menu h2
+            {
+                background-color: #88AA44;
+            }
+            .light-content,
+            .mobile .light-content
+            {
+                border: 16px solid #88AA44;
+            }
+                        .display-ad p,
+            p.start-ad,
+            .top-color,
+            .suggest-list,
+            .suggest-list li,
+            .suggest-list li span.selected,
+            .search-box {
+                background-color: #EEEEFF;
+            }
+            .top-bar,
+            .landing-top-bar
+            {
+                background: #EEEEFF;
+                background: linear-gradient(to top, #FFFFFF 0%, #EEEEFF 30%, #EEEEFF 70%);
+            }
+                    </style>
+        </head>
+<body>
+        </body>
+    </html>
\ No newline at end of file
diff --git a/tests/test_files/cms_detector/cms_detector_input.txt b/tests/test_files/cms_detector/cms_detector_input.txt
index 49a7fb6..a714df9 100644
--- a/tests/test_files/cms_detector/cms_detector_input.txt
+++ b/tests/test_files/cms_detector/cms_detector_input.txt
@@ -5,4 +5,10 @@ Drupal04.txt
 Wordpress01.txt
 Wordpress02.txt
 Wordpress03.txt
-Wordpress04.txt
\ No newline at end of file
+Wordpress04.txt
+vBulletin01.txt
+vBulletin02.txt
+Joomla01.txt
+SiteCore01.txt
+SiteCore02.txt
+Yioop01.txt
\ No newline at end of file
diff --git a/tests/test_files/cms_detector/cms_detector_results.txt b/tests/test_files/cms_detector/cms_detector_results.txt
index 56a2af1..c37ba0c 100644
--- a/tests/test_files/cms_detector/cms_detector_results.txt
+++ b/tests/test_files/cms_detector/cms_detector_results.txt
@@ -6,3 +6,9 @@ WORDPRESS
 WORDPRESS
 WORDPRESS
 WORDPRESS
+VBULLETIN
+VBULLETIN
+JOOMLA!
+SITECORE
+SITECORE
+YIOOP!
\ No newline at end of file
diff --git a/tests/test_files/cms_detector/vBulletin01.txt b/tests/test_files/cms_detector/vBulletin01.txt
new file mode 100644
index 0000000..4e63482
--- /dev/null
+++ b/tests/test_files/cms_detector/vBulletin01.txt
@@ -0,0 +1,77 @@
+﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en">
+<head>
+<!-- no cache headers -->
+<meta http-equiv="Pragma" content="no-cache" />
+<meta http-equiv="Expires" content="-1" />
+<meta http-equiv="Cache-Control" content="no-cache" />
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+
+
+	
+<meta name="keywords" content="The International Portal for Sportster and Buell Motorcycle Enthusiasts, sportsters, xlforum, sportster motorcycle forum, 883c, 1200c, hugger, harley, harley davidson, how to, buell" />
+<meta name="description" content="" />
+	
+
+<meta name="author" content="The Sportster and Buell Motorcycle Forum" />
+<meta name="copyright" content="Copyright (c) 2016 by The Sportster and Buell Motorcycle Forum" />
+<meta name="rating" content="general" />
+
+<!-- end no cache headers -->
+
+<title>The International Portal for Sportster and Buell Motorcycle Enthusiasts - The Sportster and Buell Motorcycle Forum</title>
+
+<!-- CSS Stylesheet -->
+<style type="text/css" id="vbulletin_css">
+/**
+* vBulletin 3.8.5 CSS
+* Style: 'Legacy'; Style ID: 14
+*/
+@import url("/vbportal/forums/clientscript/vbulletin_css/style-be5282e1-00014.css");
+</style>
+<link rel="stylesheet" type="text/css" href="/vbportal/forums/clientscript/vbulletin_important.css?v=385" />
+
+
+<!-- / CSS Stylesheet -->
+
+
+
+<script type="text/javascript" src="http://yui.yahooapis.com/2.7.0/build/yahoo-dom-event/yahoo-dom-event.js?v=385"></script>
+<script type="text/javascript" src="http://yui.yahooapis.com/2.7.0/build/connection/connection-min.js?v=385"></script>
+<script type="text/javascript">
+<!--
+var SESSIONURL = "";
+var SECURITYTOKEN = "guest";
+var IMGDIR_MISC = "/vbportal/forums/images/misc";
+// -->
+</script>
+
+
+
+<script type="text/javascript">
+<!--
+function openwho(threadid) {
+	return openWindow("/vbportal/forums/misc.php?" + SESSIONURL + "do=whoposted&threadid=" + threadid, 230, 300);
+}
+// -->
+</script>
+
+<script type="text/javascript" src="/vbportal/forums/clientscript/vbulletin_global.js?v=385"></script>
+<script type="text/javascript" src="/vbportal/forums/clientscript/vbulletin_menu.js?v=385"></script>
+
+<link rel="alternate" type="application/rss+xml" title="The Sportster and Buell Motorcycle Forum RSS News Feed" href="/vbportal/external.php?type=rss2" />
+
+
+
+<script type="text/javascript">
+<!--
+window.onload = function() {
+
+}
+//-->
+</script>
+
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/test_files/cms_detector/vBulletin02.txt b/tests/test_files/cms_detector/vBulletin02.txt
new file mode 100644
index 0000000..632ffd1
--- /dev/null
+++ b/tests/test_files/cms_detector/vBulletin02.txt
@@ -0,0 +1,90 @@
+﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en" id="vbulletin_html">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<meta id="e_vb_meta_bburl" name="vb_meta_bburl" content="http://ubuntuforums.org" />
+<base href="http://ubuntuforums.org/" /><!--[if IE]></base><![endif]-->
+<meta name="generator" content="vBulletin 4.2.2" />
+<meta http-equiv="X-UA-Compatible" content="IE=9" />
+
+	<link rel="Shortcut Icon" href="favicon.ico" type="image/x-icon" />
+
+
+		<meta name="keywords" content="ubuntu forums,ubuntu linux,ubuntu,linux forum,ubuntu forum,linux ubuntu,ubuntu support,ubuntu help,ubuntu linux help" />
+		<meta name="description" content="A help and support forum for Ubuntu Linux." />
+
+
+
+
+
+	
+		<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.9.0/build/yuiloader-dom-event/yuiloader-dom-event.js&amp;2.9.0/build/connection/connection-min.js"></script>
+	
+
+<script type="text/javascript">
+<!--
+	if (typeof YAHOO === 'undefined') // Load ALL YUI Local
+	{
+		document.write('<script type="text/javascript" src="clientscript/yui/yuiloader-dom-event/yuiloader-dom-event.js?v=422"><\/script>');
+		document.write('<script type="text/javascript" src="clientscript/yui/connection/connection-min.js?v=422"><\/script>');
+		var yuipath = 'clientscript/yui';
+		var yuicombopath = '';
+		var remoteyui = false;
+	}
+	else	// Load Rest of YUI remotely (where possible)
+	{
+		var yuipath = 'http://yui.yahooapis.com/2.9.0/build';
+		var yuicombopath = 'http://yui.yahooapis.com/combo';
+		var remoteyui = true;
+		if (!yuicombopath)
+		{
+			document.write('<script type="text/javascript" src="http://yui.yahooapis.com/2.9.0/build/connection/connection-min.js?v=422"><\/script>');
+		}
+	}
+	var SESSIONURL = "";
+	var SECURITYTOKEN = "guest";
+	var IMGDIR_MISC = "images/ubuntu-VB4/misc";
+	var IMGDIR_BUTTON = "images/ubuntu-VB4/buttons";
+	var vb_disable_ajax = parseInt("0", 10);
+	var SIMPLEVERSION = "422";
+	var BBURL = "http://ubuntuforums.org";
+	var LOGGEDIN = 0 > 0 ? true : false;
+	var THIS_SCRIPT = "index";
+	var RELPATH = "";
+	var PATHS = {
+		forum : "",
+		cms   : "",
+		blog  : ""
+	};
+	var AJAXBASEURL = "http://ubuntuforums.org/";
+// -->
+</script>
+<script type="text/javascript" src="http://ubuntuforums.org/clientscript/vbulletin-core.js?v=422"></script>
+
+
+
+	<link rel="alternate" type="application/rss+xml" title="Ubuntu Forums RSS Feed" href="http://ubuntuforums.org/external.php?type=RSS2" />
+	
+
+
+
+	<link rel="stylesheet" type="text/css" href="css.php?styleid=117&amp;langid=3&amp;d=1456956033&amp;td=ltr&amp;sheet=bbcode.css,editor.css,popupmenu.css,reset-fonts.css,vbulletin.css,vbulletin-chrome.css,vbulletin-formcontrols.css," />
+
+	<!--[if lt IE 8]>
+	<link rel="stylesheet" type="text/css" href="css.php?styleid=117&amp;langid=3&amp;d=1456956033&amp;td=ltr&amp;sheet=popupmenu-ie.css,vbulletin-ie.css,vbulletin-chrome-ie.css,vbulletin-formcontrols-ie.css,editor-ie.css" />
+	<![endif]-->
+
+<link href='http://fonts.googleapis.com/css?family=Ubuntu:400,400italic,700,700italic|Ubuntu+Mono:400,700' rel='stylesheet' type='text/css'>
+	<title>Ubuntu Forums</title>
+	<script type="text/javascript" src="clientscript/vbulletin_read_marker.js?v=422"></script>
+	
+	<link rel="stylesheet" type="text/css" href="css.php?styleid=117&amp;langid=3&amp;d=1456956033&amp;td=ltr&amp;sheet=forumbits.css,forumhome.css,options.css" />
+        
+	<!--[if lt IE 8]><link rel="stylesheet" type="text/css" href="css.php?styleid=117&amp;langid=3&amp;d=1456956033&amp;td=ltr&amp;sheet=forumbits-ie.css,options-ie.css" /><![endif]-->
+        
+	<link rel="stylesheet" type="text/css" href="css.php?styleid=117&amp;langid=3&amp;d=1456956033&amp;td=ltr&amp;sheet=additional.css" />
+
+</head>
+<body>
+</body>
+</html>
\ No newline at end of file
-- 
1.9.5.github.0

