Orchard
MODULES
GROWING YOUR
“

We wanted a modern CMS based on ASP.NET
MVC. We also wanted an extremely extensible
platform similar to what PHP has wi...
Content
FUNDAMENTALS
ContentFields
lly!”
y be
in m
Get
“
es ! ”
h no
“O

ContentFields
contain Data
ContentParts
eye
cary
s
arp
ig s h
b
eeth
t

ContentParts

ean!
lly m
r ea

have Behavior
ContentTypes
ContentTypes
are Consumable by Users
Types

Parts

Fields

Data
Types
Fields

Data
each
is an
a

content item
instance of
content type
Module/Feature
Module/Feature
features extend the CMS
modules contain and distribute
Module
FUNDAMENTALS
RecordsAndParts
records are Data Models
parts are View Models
HandlersAndDrivers
handlers give behavior to the code
drivers are are specialized controllers
ShapeTemplates
DependencyInjection
Controllers
ModuleManifest
Manifest [Module|Theme]

“

A manifest stores metadata that Orchard
uses to describe modules and themes to the
system, suc...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Name: AntiSpam
AntiForgery: enabled
Author: The Orchard Team
Website: http:/...
Orchard
EXECUTABLE
WebSiteAndConsoleApp
“

The Orchard command interpreter supports
running a few built-in commands as well as
specific commands from enabled feat...
C:OrchardsrcOrchard.Webbin> Orchard.exe_
C:OrchardsrcOrchard.Webbin> Orchard.exe
Initializing Orchard session. (This might take a few
seconds...)
Type "?" for help...
orchard> call me "Rock God"
Error executing command "call me Rock God"
---------------------------------------------------...
Module
DEVELOPMENT
1
#
p
te
S
od
C

io n
at
ner
Ge
e
CodeGeneration
orchard> feature enable Orchard.CodeGeneration_
orchard> feature enable Orchard.CodeGeneration
Enabling features Orchard.CodeGeneration
Code Generation was enabled
orchar...
orchard> feature enable Orchard.CodeGeneration
Enabling features Orchard.CodeGeneration
Code Generation was enabled
orchar...
orchard> feature enable Orchard.CodeGeneration
Enabling features Orchard.CodeGeneration
Code Generation was enabled
orchar...
ReadyGo
UpdateManifest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Name: SlideShare
AntiForgery: enabled
Author: Jay Harris, Arana Software
Web...
2
#
p
te
S
ec
R

r ts
Pa
s&
rd
o
RecordsAndParts
AddNewClass
1
2
3
4 namespace SlideShare.Models {
public class SlideShareRecord {
5
6
7
}
8
9 }
10
1 using Orchard.ContentManagement.Records;
2
3
4 namespace SlideShare.Models {
public class SlideShareRecord : ContentPart...
using Orchard.ContentManagement.Records;
1
2
3
4 namespace SlideShare.Models {
public class SlideShareRecord : ContentPart...
se
U

ce
spa
me
Na

S
EL
D
O or
M
S
D
R
O
C
E
R
ies
rt
pe
ro
kP
ar
M

L
A
U
T
IR
V
ur
Yo
nd
Mi

S
S
LA
C
E
S
A
B
1 using Orchard.ContentManagement.Records;
2
3
4 namespace SlideShare.Models {
public class SlideShareRecord : ContentPart...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
na...
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public class SlideSharePart :
ContentPart<SlideShareRecord> {
public stri...
le
i
p
m
o
C

i ng
ed
oce
Pr
ore
ef
B
3
#
p
te
S
Da

io n
at
i gr
aM
t
orchard> codegen datamigration SlideShare_
orchard> codegen datamigration SlideShare
Creating Data Migration for SlideShare
Data migration created successfully in Mo...
DataMigration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

using System.Data;
using Orchard.Data.Migration;
namespace SlideShare {
publ...
11
12 !!!!
13 !!!!

// column methods can use generic types...
.Column<string>("SlideShareId")
.Column<int>("StartFromSlid...
5
6

public class Migrations : DataMigrationImpl {
public int Create() {
// The ‘return’ is for versioning
return 1;

14
1...
Attachable [Content Part]

“

Indicates if administrators can attach the
Content Part to any Content Type, or if the
Part ...
1
2
3
4
5
6
7
8

using
using
using
using
using

System.Data;
Orchard.ContentManagement.MetaData;
Orchard.Core.Contents.Ext...
1
2
3
4
5
6
7
8

using
using
using
using
using

System.Data;
Orchard.ContentManagement.MetaData;
Orchard.Core.Contents.Ext...
4
#
p
te
S
and
H

ers
r iv
&D
ers
l
CreateHandler
Part Handlers

“

Most Handlers will be very simple behavior
managers. Often, the only behavior they will
have to specify ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

using SlideShare.Models;
using Orchard.ContentManagement.Handlers;
using Orc...
CreateDriver
1
2
3
4
5
6
7
8
9
10
11
12
13

using
using
using
using

System;
Orchard.ContentManagement;
Orchard.ContentManagement.Drive...
10
11
12
13
14
15
16
17
18
19
20
21

protected override DriverResult Display(
SlideSharePart part,
string displayType,
dyn...
23
24
25
26
27
28
29
30
31
32
33
34
35

//GET
protected override DriverResult Editor(
SlideSharePart part,
dynamic shapeHe...
37
38
39
40
41
42
43
44
45

//POST
protected override DriverResult Editor(
SlideSharePart part,
IUpdateModel updater,
dyna...
5
#
p
te
S
ap
Sh

tes
pl a
Tem
e
DisplayTemplate
ur
Yo
nd
Mi

V

S
H
T
A
P
W
IE
10
11
12
13
14
15
16
17
18
19
20
21

protected override DriverResult Display(
SlideSharePart part,
string displayType,
dyn...
“Parts_SlideShare”
translates to

Views/Parts/SlideShare.cshtml
1
2
3
4
5
6
7
8
9
10

<iframe src="http://www.slideshare.net/slideshow/
embed_code/13888742?startSlide=8"
width="427" heig...
1 @if(!string.IsNullOrWhiteSpace(Model.SlideShareId as
string))
2 {
<iframe src="http://www.slideshare.net/slideshow/
3
em...
1 @if(!string.IsNullOrWhiteSpace(Model.SlideShareId as
string))
2 {
var startSlide = Model.StartFromSlide as int?;
3
var s...
EditorTemplate
23
24
25
26
27
28
29
30
31
32
33
34
35

//GET
protected override DriverResult Editor(
SlideSharePart part,
dynamic shapeHe...
“Parts/SlideShare”
translates to

Views/EditorTemplates/
Parts/SlideShare.cshtml
1 @model SlideShare.Models.SlideSharePart
2 <fieldset>
<legend>SlideShare Fields</legend>
3
<div class="editor-label">
4
@...
1 @model SlideShare.Models.SlideSharePart
2 <fieldset>
<legend>SlideShare Fields</legend>
3
<div class="editor-label">
4
@...
ShapePlacement
1 <Placement>
<Place Parts_SlideShare="Content:10" />
2
<Place Parts_SlideShare_Edit="Content:8" />
3
4 </Placement>
5
6
7...
te
ra
b
le
e
C
EnableFeature
AddContentDone
Module
DISTRIBUTION
OrchardPackaging
orchard> feature enable Orchard.Packaging
Enabling features Orchard.Packaging
Packaging was enabled
orchard> _
orchard> feature enable Orchard.Packaging
Enabling features Orchard.Packaging
Packaging was enabled
orchard> package creat...
orchard> feature enable Orchard.Packaging
Enabling features Orchard.Packaging
Packaging was enabled
orchard> package creat...
“

”

...you’ll be able to achieve all kinds of
glory and fame.
orchardproject.net
Profit
github.com/aranasoft
bit.ly/growingOrchard
“

”

...you’ll be able to achieve all kinds of
glory and fame.
orchardproject.net
jay harris

P R E S I D E N T

jay@aranasoft.com
#growingOrchard
@jayharris
Thanks
OrchardCMS module development
OrchardCMS module development
OrchardCMS module development
OrchardCMS module development
OrchardCMS module development
OrchardCMS module development
OrchardCMS module development
Upcoming SlideShare
Loading in …5
×

OrchardCMS module development

5,315 views

Published on

So, you need a Content Management System on the .NET framework. While your business might spend wheelbarrows of money on a platform that is powerful and extensible, your personal site would abandon extensibility for a free, open-source solution. But what if we had an option that was free and powerful and extensible? We do: Orchard CMS. Since we already know that Orchard is free, in this session we will discuss the power of Orchard’s CMS engine. You will learn how to build new modules for the Orchard platform, allowing you to extend functionality as you see fit to meet the needs of your site, your business, and customers.

Published in: Technology

OrchardCMS module development

  1. 1. Orchard MODULES GROWING YOUR
  2. 2. “ We wanted a modern CMS based on ASP.NET MVC. We also wanted an extremely extensible platform similar to what PHP has with Drupal. But most importantly we wanted to build better bridges between our team at Microsoft and the open-source community. ” Betrand Le Roy, Creator of Orchard
  3. 3. Content FUNDAMENTALS
  4. 4. ContentFields
  5. 5. lly!” y be in m Get “ es ! ” h no “O ContentFields contain Data
  6. 6. ContentParts
  7. 7. eye cary s arp ig s h b eeth t ContentParts ean! lly m r ea have Behavior
  8. 8. ContentTypes
  9. 9. ContentTypes are Consumable by Users
  10. 10. Types Parts Fields Data
  11. 11. Types Fields Data
  12. 12. each is an a content item instance of content type
  13. 13. Module/Feature
  14. 14. Module/Feature features extend the CMS modules contain and distribute
  15. 15. Module FUNDAMENTALS
  16. 16. RecordsAndParts records are Data Models parts are View Models
  17. 17. HandlersAndDrivers handlers give behavior to the code drivers are are specialized controllers
  18. 18. ShapeTemplates
  19. 19. DependencyInjection
  20. 20. Controllers
  21. 21. ModuleManifest
  22. 22. Manifest [Module|Theme] “ A manifest stores metadata that Orchard uses to describe modules and themes to the system, such as name, version, description, author, and tags. ” docs.orchardproject.net
  23. 23. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Name: AntiSpam AntiForgery: enabled Author: The Orchard Team Website: http://orchardproject.net Version: 1.7.2 OrchardVersion: 1.7.2 Description: Provides anti-spam services to protect your Features: Orchard.AntiSpam: Name: Anti-Spam Description: Provides anti-spam services to prot Category: Security Dependencies: Orchard.Tokens, Orchard.jQuery Akismet.Filter: Name: Akismet Anti-Spam Filter Description: Provides an anti-spam filter based Category: Security Dependencies: Orchard.AntiSpam
  24. 24. Orchard EXECUTABLE
  25. 25. WebSiteAndConsoleApp
  26. 26. “ The Orchard command interpreter supports running a few built-in commands as well as specific commands from enabled features of an Orchard installation. ” Orchard.exe
  27. 27. C:OrchardsrcOrchard.Webbin> Orchard.exe_
  28. 28. C:OrchardsrcOrchard.Webbin> Orchard.exe Initializing Orchard session. (This might take a few seconds...) Type "?" for help, "exit" to exit, "cls" to clear screen orchard> _
  29. 29. orchard> call me "Rock God" Error executing command "call me Rock God" ---------------------------------------------------------No command found matching arguments "call me Rock God". Commands available: site setting set baseurl, autoroute create, theme list, theme activate, widget create, layer create, menuitem create, menu create, blog create, blog import, ...
  30. 30. Module DEVELOPMENT
  31. 31. 1 # p te S od C io n at ner Ge e
  32. 32. CodeGeneration
  33. 33. orchard> feature enable Orchard.CodeGeneration_
  34. 34. orchard> feature enable Orchard.CodeGeneration Enabling features Orchard.CodeGeneration Code Generation was enabled orchard> _
  35. 35. orchard> feature enable Orchard.CodeGeneration Enabling features Orchard.CodeGeneration Code Generation was enabled orchard> codegen module SlideShare /IncludeInSolution:true_
  36. 36. orchard> feature enable Orchard.CodeGeneration Enabling features Orchard.CodeGeneration Code Generation was enabled orchard> codegen module SlideShare /IncludeInSolution:true Creating Module SlideShare Module SlideShare created successfully orchard> _
  37. 37. ReadyGo
  38. 38. UpdateManifest
  39. 39. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Name: SlideShare AntiForgery: enabled Author: Jay Harris, Arana Software Website: http://www.aranasoft.com Version: 1.0 OrchardVersion: 1.0 Description: Provide SlideShare presentations as an atta Features: SlideShare: Description: Provide SlideShare presentations as an Category: Social
  40. 40. 2 # p te S ec R r ts Pa s& rd o
  41. 41. RecordsAndParts AddNewClass
  42. 42. 1 2 3 4 namespace SlideShare.Models { public class SlideShareRecord { 5 6 7 } 8 9 } 10
  43. 43. 1 using Orchard.ContentManagement.Records; 2 3 4 namespace SlideShare.Models { public class SlideShareRecord : ContentPartRecord { 5 6 7 } 8 9 } 10
  44. 44. using Orchard.ContentManagement.Records; 1 2 3 4 namespace SlideShare.Models { public class SlideShareRecord : ContentPartRecord { 5 public virtual string SlideShareId { get; set; } 6 public virtual int? StartFromSlide { get; set; } 7 } 8 9 } 10
  45. 45. se U ce spa me Na S EL D O or M S D R O C E R
  46. 46. ies rt pe ro kP ar M L A U T IR V
  47. 47. ur Yo nd Mi S S LA C E S A B
  48. 48. 1 using Orchard.ContentManagement.Records; 2 3 4 namespace SlideShare.Models { public class SlideShareRecord : ContentPartRecord { 5 public virtual string SlideShareId { get; set; } 6 public virtual int? StartFromSlide { get; set; } 7 } 8 9 } 10
  49. 49. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using Orchard.ContentManagement; using Orchard.ContentManagement.Records; namespace SlideShare.Models { public class SlideShareRecord : ContentPartRecord { public virtual string SlideShareId { get; set; } public virtual int? StartFromSlide { get; set; } } public class SlideSharePart : ContentPart<SlideShareRecord> { } }
  50. 50. 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class SlideSharePart : ContentPart<SlideShareRecord> { public string SlideShareId { get { return Record.SlideShareId; } set { Record.SlideShareId = value; } } [Range(1, 20)] public int? StartFromSlide { get { return Record.StartFromSlide; } set { Record.StartFromSlide = value; } } }
  51. 51. le i p m o C i ng ed oce Pr ore ef B
  52. 52. 3 # p te S Da io n at i gr aM t
  53. 53. orchard> codegen datamigration SlideShare_
  54. 54. orchard> codegen datamigration SlideShare Creating Data Migration for SlideShare Data migration created successfully in Module SlideShare orchard> _
  55. 55. DataMigration
  56. 56. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using System.Data; using Orchard.Data.Migration; namespace SlideShare { public class Migrations : DataMigrationImpl { public int Create() { // Creating table SlideShareRecord SchemaBuilder.CreateTable("SlideShareRecord", table => table !!!! .ContentPartRecord() !!!! .Column("SlideShareId", DbType.String) !!!! .Column("StartFromSlide", DbType.Int32) ); return 1; } } }
  57. 57. 11 12 !!!! 13 !!!! // column methods can use generic types... .Column<string>("SlideShareId") .Column<int>("StartFromSlide") 11 12 !!!! 13 !!!! // or they can use a DbType argument. .Column("SlideShareId", DbType.String) .Column("StartFromSlide", DbType.Int32) // either way. just not both.
  58. 58. 5 6 public class Migrations : DataMigrationImpl { public int Create() { // The ‘return’ is for versioning return 1; 14 15 16 } public int UpdateFrom1() { // Do some data migrations return 2; } public int UpdateFrom2() { // More data migrations return 3; } 25 26 27 33 34 35 }
  59. 59. Attachable [Content Part] “ Indicates if administrators can attach the Content Part to any Content Type, or if the Part is limited only to Types explicitly specified by the Module. ”
  60. 60. 1 2 3 4 5 6 7 8 using using using using using System.Data; Orchard.ContentManagement.MetaData; Orchard.Core.Contents.Extensions; Orchard.Data.Migration; SlideShare.Models; namespace SlideShare { public class Migrations : DataMigrationImpl { public int UpdateFrom1() { 17 ContentDefinitionManager.AlterPartDefinition( 18 "SlideSharePart", cfg => cfg.Attachable()); 19 return 2; 20 } 21 } 22 23 } 24
  61. 61. 1 2 3 4 5 6 7 8 using using using using using System.Data; Orchard.ContentManagement.MetaData; Orchard.Core.Contents.Extensions; Orchard.Data.Migration; SlideShare.Models; namespace SlideShare { public class Migrations : DataMigrationImpl { public int UpdateFrom1() { 17 ContentDefinitionManager.AlterPartDefinition( 18 typeof(SlideSharePart).Name, cfg => cfg.Attachable( 19 return 2; 20 } 21 } 22 23 } 24
  62. 62. 4 # p te S and H ers r iv &D ers l
  63. 63. CreateHandler
  64. 64. Part Handlers “ Most Handlers will be very simple behavior managers. Often, the only behavior they will have to specify is how data is to be stored. ”
  65. 65. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using SlideShare.Models; using Orchard.ContentManagement.Handlers; using Orchard.Data; namespace SlideShare.Handlers { public class SlideShareHandler : ContentHandler { public SlideShareHandler( IRepository<SlideShareRecord> repository) { Filters.Add(StorageFilter.For(repository)); } } }
  66. 66. CreateDriver
  67. 67. 1 2 3 4 5 6 7 8 9 10 11 12 13 using using using using System; Orchard.ContentManagement; Orchard.ContentManagement.Drivers; SlideShare.Models; namespace SlideShare.Drivers { public class SlideShareDriver : ContentPartDriver<SlideSharePart> { } }
  68. 68. 10 11 12 13 14 15 16 17 18 19 20 21 protected override DriverResult Display( SlideSharePart part, string displayType, dynamic shapeHelper) { return ContentShape("Parts_SlideShare", () => shapeHelper.Parts_SlideShare( SlideShareId: part.SlideShareId, StartFromSlide: part.StartFromSlide ) ); }
  69. 69. 23 24 25 26 27 28 29 30 31 32 33 34 35 //GET protected override DriverResult Editor( SlideSharePart part, dynamic shapeHelper) { return ContentShape("Parts_SlideShare_Edit", () => shapeHelper.EditorTemplate( TemplateName: "Parts/SlideShare", Model: part, Prefix: Prefix ) ); }
  70. 70. 37 38 39 40 41 42 43 44 45 //POST protected override DriverResult Editor( SlideSharePart part, IUpdateModel updater, dynamic shapeHelper) { updater.TryUpdateModel(part, Prefix, null, null); return Editor(part, shapeHelper); }
  71. 71. 5 # p te S ap Sh tes pl a Tem e
  72. 72. DisplayTemplate
  73. 73. ur Yo nd Mi V S H T A P W IE
  74. 74. 10 11 12 13 14 15 16 17 18 19 20 21 protected override DriverResult Display( SlideSharePart part, string displayType, dynamic shapeHelper) { return ContentShape("Parts_SlideShare", () => shapeHelper.Parts_SlideShare( SlideShareId: part.SlideShareId, StartFromSlide: part.StartFromSlide ) ); }
  75. 75. “Parts_SlideShare” translates to Views/Parts/SlideShare.cshtml
  76. 76. 1 2 3 4 5 6 7 8 9 10 <iframe src="http://www.slideshare.net/slideshow/ embed_code/13888742?startSlide=8" width="427" height="356" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" allowfullscreen style="border: 1px solid #CCC; border-width: 1px 1px 0; margin-bottom: 5px"> </iframe>
  77. 77. 1 @if(!string.IsNullOrWhiteSpace(Model.SlideShareId as string)) 2 { <iframe src="http://www.slideshare.net/slideshow/ 3 embed_code/13888742?startSlide=8" width="427" height="356" frameborder="0" 4 marginwidth="0" marginheight="0" 5 scrolling="no" allowfullscreen 6 style="border: 1px solid #CCC; border-width: 1px 1px 7 0; margin-bottom: 5px"> </iframe> 8 9 }
  78. 78. 1 @if(!string.IsNullOrWhiteSpace(Model.SlideShareId as string)) 2 { var startSlide = Model.StartFromSlide as int?; 3 var startSlideQS = startSlide.HasValue 4 ? string.Format("startSlide={0}", startSlide.Value) 5 : string.Empty; 6 7 var slideShareUrl = string.Format( 8 "http://www.slideshare.net/slideshow/embed_code/ 9 {0}?{1}", Model.SlideShareId, 10 startSlideQS); 11 12 <iframe src="@slideShareUrl" 13 width="427" height="356" frameborder="0" 14 marginwidth="0" marginheight="0" 15 scrolling="no" allowfullscreen 16
  79. 79. EditorTemplate
  80. 80. 23 24 25 26 27 28 29 30 31 32 33 34 35 //GET protected override DriverResult Editor( SlideSharePart part, dynamic shapeHelper) { return ContentShape("Parts_SlideShare_Edit", () => shapeHelper.EditorTemplate( TemplateName: "Parts/SlideShare", Model: part, Prefix: Prefix ) ); }
  81. 81. “Parts/SlideShare” translates to Views/EditorTemplates/ Parts/SlideShare.cshtml
  82. 82. 1 @model SlideShare.Models.SlideSharePart 2 <fieldset> <legend>SlideShare Fields</legend> 3 <div class="editor-label"> 4 @Html.LabelFor(m => m.SlideShareId) 5 </div> 6 <div class="editor-field"> 7 @Html.TextBoxFor(m => m.SlideShareId) 8 @Html.ValidationMessageFor(m => m.SlideShareId) 9 </div> 10 11 </fieldset>
  83. 83. 1 @model SlideShare.Models.SlideSharePart 2 <fieldset> <legend>SlideShare Fields</legend> 3 <div class="editor-label"> 4 @Html.LabelFor(m => m.SlideShareId) 5 </div> 6 <div class="editor-field"> 7 @Html.TextBoxFor(m => m.SlideShareId) 8 @Html.ValidationMessageFor(m => m.SlideShareId) 9 </div> 10 <div class="editor-label"> 11 @Html.LabelFor(m => m.StartFromSlide) 12 </div> 13 <div class="editor-field"> 14 @Html.TextBoxFor(m => m.StartFromSlide) 15 @Html.ValidationMessageFor(m => m.StartFromSlide) 16 </div> 17 18 </fieldset>
  84. 84. ShapePlacement
  85. 85. 1 <Placement> <Place Parts_SlideShare="Content:10" /> 2 <Place Parts_SlideShare_Edit="Content:8" /> 3 4 </Placement> 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  86. 86. te ra b le e C
  87. 87. EnableFeature
  88. 88. AddContentDone
  89. 89. Module DISTRIBUTION
  90. 90. OrchardPackaging
  91. 91. orchard> feature enable Orchard.Packaging Enabling features Orchard.Packaging Packaging was enabled orchard> _
  92. 92. orchard> feature enable Orchard.Packaging Enabling features Orchard.Packaging Packaging was enabled orchard> package create SlideShare C:Code_
  93. 93. orchard> feature enable Orchard.Packaging Enabling features Orchard.Packaging Packaging was enabled orchard> package create SlideShare C:Code Package "C:CodeOrchard.Module.SlideShare.1.0.nupkg" successfully created orchard> _
  94. 94. “ ” ...you’ll be able to achieve all kinds of glory and fame. orchardproject.net
  95. 95. Profit
  96. 96. github.com/aranasoft
  97. 97. bit.ly/growingOrchard
  98. 98. “ ” ...you’ll be able to achieve all kinds of glory and fame. orchardproject.net
  99. 99. jay harris P R E S I D E N T jay@aranasoft.com #growingOrchard @jayharris
  100. 100. Thanks

×